adding Daniel Carl's authorship notice
[vimprobable/e.git] / main.c
blob03322c16d76ca64c93bcd635c43b1fb826329170
2 /*
3 (c) 2009 by Leon Winter
4 (c) 2009-2011 by Hannes Schueller
5 (c) 2009-2010 by Matto Fransen
6 (c) 2010-2011 by Hans-Peter Deifel
7 (c) 2010-2011 by Thomas Adam
8 (c) 2011 by Albert Kim
9 (c) 2011 by Daniel Carl
10 see LICENSE file
13 #include <X11/Xlib.h>
14 #include "includes.h"
15 #include "vimprobable.h"
16 #include "utilities.h"
17 #include "callbacks.h"
18 #include "javascript.h"
20 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
21 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
22 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
24 /* remove unused bits, numlock symbol and buttons from keymask */
25 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
27 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
28 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
29 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
31 /* callbacks here */
32 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
33 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
34 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
35 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
36 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
37 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
38 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
39 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
40 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
41 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
42 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
43 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
44 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
45 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
46 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
47 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
48 static gboolean webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
49 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
50 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
51 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
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];
123 static char **args;
124 static unsigned int mode = ModeNormal;
125 static unsigned int count = 0;
126 static float zoomstep;
127 static char *modkeys;
128 static char current_modkey;
129 static char *search_handle;
130 static gboolean search_direction;
131 static gboolean echo_active = TRUE;
132 WebKitWebInspector *inspector;
134 static GdkNativeWindow embed = 0;
135 static char *configfile = NULL;
136 static char *winid = NULL;
138 static char rememberedURI[1024] = "";
139 static char followTarget[8] = "";
140 char *error_msg = NULL;
142 GList *activeDownloads;
144 #include "config.h"
145 #include "keymap.h"
147 char commandhistory[COMMANDHISTSIZE][255];
148 int lastcommand = 0;
149 int maxcommands = 0;
150 int commandpointer = 0;
151 KeyList *keylistroot = NULL;
153 /* Cookie support. */
154 #ifdef ENABLE_COOKIE_SUPPORT
155 static SoupCookieJar *session_cookie_jar = NULL;
156 static SoupCookieJar *file_cookie_jar = NULL;
157 static time_t cookie_timeout = 4800;
158 static char *cookie_store;
159 static void setup_cookies(void);
160 static const char *get_cookies(SoupURI *soup_uri);
161 static void load_all_cookies(void);
162 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
163 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
164 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
165 #endif
166 /* callbacks */
167 void
168 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
169 quit(NULL);
172 void
173 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
174 gtk_window_set_title(window, title);
177 void
178 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
179 #ifdef ENABLE_GTK_PROGRESS_BAR
180 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
181 #endif
182 update_state();
185 #ifdef ENABLE_WGET_PROGRESS_BAR
186 void
187 ascii_bar(int total, int state, char *string) {
188 int i;
190 for (i = 0; i < state; i++)
191 string[i] = progressbartickchar;
192 string[i++] = progressbarcurrent;
193 for (; i < total; i++)
194 string[i] = progressbarspacer;
195 string[i] = '\0';
197 #endif
199 void
200 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
201 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
202 const char *uri = webkit_web_view_get_uri(webview);
204 update_url(uri);
205 script(&a);
208 void
209 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
210 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_INPUT_FOCUS) };
212 if (HISTORY_MAX_ENTRIES > 0)
213 history();
214 update_state();
215 script(&a);
218 static gboolean
219 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
220 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
221 if (strlen(rememberedURI) > 0) {
222 a.s = rememberedURI;
224 open_arg(&a);
225 return FALSE;
228 gboolean
229 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
230 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
231 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
232 open_arg(&a);
233 webkit_web_policy_decision_ignore(decision);
234 return TRUE;
237 gboolean
238 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
239 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
240 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
241 webkit_web_policy_decision_download(decision);
242 return TRUE;
243 } else {
244 return FALSE;
248 static WebKitWebView*
249 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
250 gchar* inspector_title;
251 GtkWidget* inspector_window;
252 GtkWidget* inspector_view;
254 /* just enough code to show the inspector - no signal handling etc. */
255 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
256 if (embed) {
257 inspector_window = gtk_plug_new(embed);
258 } else {
259 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
260 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
262 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
263 g_free(inspector_title);
264 inspector_view = webkit_web_view_new();
265 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
266 gtk_widget_show_all(inspector_window);
267 return WEBKIT_WEB_VIEW(inspector_view);
270 gboolean
271 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
272 const gchar *filename;
273 gchar *uri, *path;
274 uint32_t size;
275 Arg a;
277 filename = webkit_download_get_suggested_filename(download);
278 if (filename == NULL || strlen(filename) == 0) {
279 filename = "vimprobable_download";
281 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
282 uri = g_strconcat("file://", path, NULL);
283 webkit_download_set_destination_uri(download, uri);
284 g_free(uri);
285 size = (uint32_t)webkit_download_get_total_size(download);
286 a.i = Info;
287 if (size > 0)
288 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
289 else
290 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
291 echo(&a);
292 g_free(a.s);
293 activeDownloads = g_list_prepend(activeDownloads, download);
294 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
295 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
296 update_state();
297 return TRUE;
300 void
301 download_progress(WebKitDownload *d, GParamSpec *pspec) {
302 Arg a;
303 WebKitDownloadStatus status = webkit_download_get_status(d);
305 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
306 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
307 a.i = Error;
308 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
309 echo(&a);
310 } else {
311 a.i = Info;
312 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
313 echo(&a);
315 g_free(a.s);
316 activeDownloads = g_list_remove(activeDownloads, d);
318 update_state();
322 gboolean
323 process_keypress(GdkEventKey *event) {
324 KeyList *current;
326 current = keylistroot;
327 while (current != NULL) {
328 if (current->Element.mask == CLEAN(event->state)
329 && (current->Element.modkey == current_modkey
330 || (!current->Element.modkey && !current_modkey)
331 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
332 && current->Element.key == event->keyval
333 && current->Element.func)
334 if (current->Element.func(&current->Element.arg)) {
335 current_modkey = count = 0;
336 update_state();
337 return TRUE;
339 current = current->next;
341 return FALSE;
344 gboolean
345 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
346 Arg a = { .i = ModeNormal, .s = NULL };
348 switch (mode) {
349 case ModeNormal:
350 if (CLEAN(event->state) == 0) {
351 if (IS_ESCAPE(event)) {
352 a.i = Info;
353 a.s = g_strdup("");
354 echo(&a);
355 g_free(a.s);
356 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
357 || (event->keyval == GDK_0 && count))) {
358 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
359 update_state();
360 return TRUE;
361 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
362 current_modkey = event->keyval;
363 update_state();
364 return TRUE;
367 /* keybindings */
368 if (process_keypress(event) == TRUE) return TRUE;
370 break;
371 case ModeInsert:
372 if (IS_ESCAPE(event)) {
373 a.i = Silent;
374 a.s = g_strdup("hints.clearFocus();");
375 script(&a);
376 a.i = ModeNormal;
377 return set(&a);
379 case ModePassThrough:
380 if (IS_ESCAPE(event)) {
381 echo(&a);
382 set(&a);
383 return TRUE;
385 break;
386 case ModeSendKey:
387 echo(&a);
388 set(&a);
389 break;
391 return FALSE;
394 void
395 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
396 const char *fg_color_str) {
397 GdkColor fg_color;
398 GdkColor bg_color;
399 PangoFontDescription *font;
401 font = pango_font_description_from_string(font_str);
402 gtk_widget_modify_font(widget, font);
403 pango_font_description_free(font);
405 if (fg_color_str)
406 gdk_color_parse(fg_color_str, &fg_color);
407 if (bg_color_str)
408 gdk_color_parse(bg_color_str, &bg_color);
410 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
411 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
413 return;
416 void
417 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
418 const char *uri = webkit_web_view_get_uri(webview);
420 memset(rememberedURI, 0, 1024);
421 if (link) {
422 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link));
423 strncpy(rememberedURI, link, 1024);
424 } else
425 update_url(uri);
428 gboolean
429 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
430 Arg a;
432 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
433 if (gtk_window_has_toplevel_focus(window)) {
434 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
435 a.i = ModeNormal;
436 return set(&a);
437 } else if (!strcmp(message, "insertmode_on")) {
438 a.i = ModeInsert;
439 return set(&a);
442 return FALSE;
445 void
446 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
447 char *text;
448 guint16 length = gtk_entry_get_text_length(entry);
449 Arg a;
450 gboolean success = FALSE, forward = FALSE;
452 a.i = HideCompletion;
453 complete(&a);
454 if (length == 0)
455 return;
456 text = (char*)gtk_entry_get_text(entry);
457 if (length > 1 && text[0] == ':') {
458 success = process_line((text + 1));
459 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
460 webkit_web_view_unmark_text_matches(webview);
461 #ifdef ENABLE_MATCH_HIGHLITING
462 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
463 webkit_web_view_set_highlight_text_matches(webview, TRUE);
464 #endif
465 count = 0;
466 #ifndef ENABLE_INCREMENTAL_SEARCH
467 a.s =& text[1];
468 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
469 search(&a);
470 #else
471 search_direction = forward;
472 search_handle = g_strdup(&text[1]);
473 #endif
474 } else if (text[0] == '.' || text[0] == ',') {
475 a.i = Silent;
476 a.s = g_strdup_printf("hints.fire();");
477 script(&a);
478 update_state();
479 } else
480 return;
481 if (!echo_active)
482 gtk_entry_set_text(entry, "");
483 gtk_widget_grab_focus(GTK_WIDGET(webview));
486 gboolean
487 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
488 Arg a;
489 int numval;
491 if (mode == ModeHints) {
492 if (event->keyval == GDK_Tab) {
493 a.i = Silent;
494 a.s = g_strdup_printf("hints.focusNextHint();");
495 script(&a);
496 update_state();
497 return TRUE;
499 if (event->keyval == GDK_ISO_Left_Tab) {
500 a.i = Silent;
501 a.s = g_strdup_printf("hints.focusPreviousHint();");
502 script(&a);
503 update_state();
504 return TRUE;
506 if (event->keyval == GDK_Return) {
507 a.i = Silent;
508 a.s = g_strdup_printf("hints.fire();");
509 script(&a);
510 update_state();
511 return TRUE;
514 switch (event->keyval) {
515 case GDK_bracketleft:
516 case GDK_Escape:
517 if (!IS_ESCAPE(event)) break;
518 a.i = HideCompletion;
519 complete(&a);
520 a.i = ModeNormal;
521 return set(&a);
522 break;
523 case GDK_Tab:
524 a.i = DirectionNext;
525 return complete(&a);
526 break;
527 case GDK_Up:
528 a.i = DirectionPrev;
529 return commandhistoryfetch(&a);
530 break;
531 case GDK_Down:
532 a.i = DirectionNext;
533 return commandhistoryfetch(&a);
534 break;
535 case GDK_ISO_Left_Tab:
536 a.i = DirectionPrev;
537 return complete(&a);
538 break;
541 if (mode == ModeHints) {
542 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
543 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
544 (event->keyval == GDK_BackSpace)) {
545 count /= 10;
546 a.i = Silent;
547 a.s = g_strdup_printf("hints.updateHints(%d);", count);
548 script(&a);
549 update_state();
550 return TRUE;
553 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
554 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
555 /* allow a zero as non-first number */
556 count = (count ? count * 10 : 0) + numval;
557 a.i = Silent;
558 a.s = g_strdup_printf("hints.updateHints(%d);", count);
559 script(&a);
560 update_state();
561 return TRUE;
565 return FALSE;
568 gboolean
569 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
570 int i;
571 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
572 /* handle mouse click events */
573 for (i = 0; i < LENGTH(mouse); i++) {
574 if (mouse[i].mask == CLEAN(event->button.state)
575 && (mouse[i].modkey == current_modkey
576 || (!mouse[i].modkey && !current_modkey)
577 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
578 && mouse[i].button == event->button.button
579 && mouse[i].func) {
580 if (mouse[i].func(&mouse[i].arg)) {
581 current_modkey = count = 0;
582 update_state();
583 return TRUE;
588 return FALSE;
591 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
592 Arg a;
593 guint16 length = gtk_entry_get_text_length(entry);
595 if (!length) {
596 a.i = HideCompletion;
597 complete(&a);
598 a.i = ModeNormal;
599 return set(&a);
601 return FALSE;
604 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
605 Arg a;
606 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
607 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
608 gboolean forward = FALSE;
610 /* Update incremental search if the user changes the search text.
612 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
613 * from the user. But if the entry is focused and the text is set
614 * through gtk_entry_set_text() in some asyncrounous operation,
615 * I would consider that a bug.
618 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
619 webkit_web_view_unmark_text_matches(webview);
620 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
621 return TRUE;
622 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
623 (text[0] == '.' || text[0] == ',')) {
624 a.i = Silent;
625 switch (text[0]) {
626 case '.':
627 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
628 break;
630 case ',':
631 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
632 break;
634 count = 0;
635 script(&a);
637 return TRUE;
638 } else if (length == 0 && followTarget[0]) {
639 mode = ModeNormal;
640 a.i = Silent;
641 a.s = g_strdup("hints.clearHints();");
642 script(&a);
643 count = 0;
644 update_state();
647 return FALSE;
650 /* funcs here */
652 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
653 memset(suggline, 0, 512);
654 strncpy(suggline, command, 512);
655 strncat(suggline, " ", 1);
656 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
659 GtkWidget * fill_eventbox(const char * completion_line) {
660 GtkBox * row;
661 GtkWidget *row_eventbox, *el;
662 GdkColor color;
663 char * markup;
665 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
666 row_eventbox = gtk_event_box_new();
667 gdk_color_parse(completionbgcolor[0], &color);
668 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
669 el = gtk_label_new(NULL);
670 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
671 g_markup_escape_text(completion_line, strlen(completion_line)), "</span>", NULL);
672 gtk_label_set_markup(GTK_LABEL(el), markup);
673 g_free(markup);
674 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
675 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
676 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
677 return row_eventbox;
680 gboolean
681 complete(const Arg *arg) {
682 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
683 size_t listlen, len, cmdlen;
684 int i, spacepos;
685 Listelement *elementlist = NULL, *elementpointer;
686 gboolean highlight = FALSE;
687 GtkBox *row;
688 GtkWidget *row_eventbox, *el;
689 GtkBox *_table;
690 GdkColor color;
691 static GtkWidget *table, *top_border;
692 static char *prefix;
693 static char **suggestions;
694 static GtkWidget **widgets;
695 static int n = 0, m, current = -1;
697 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
698 len = strlen(str);
700 /* Get the length of the list of commands for completion. We need this to
701 * malloc/realloc correctly.
703 listlen = LENGTH(commands);
705 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
706 return TRUE;
707 if (prefix) {
708 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
709 gdk_color_parse(completionbgcolor[0], &color);
710 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
711 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
712 if ((arg->i == DirectionNext && current == 0)
713 || (arg->i == DirectionPrev && current == n - 1))
714 current = -1;
715 } else {
716 free(widgets);
717 free(suggestions);
718 free(prefix);
719 gtk_widget_destroy(GTK_WIDGET(table));
720 gtk_widget_destroy(GTK_WIDGET(top_border));
721 table = NULL;
722 widgets = NULL;
723 suggestions = NULL;
724 prefix = NULL;
725 n = 0;
726 current = -1;
727 if (arg->i == HideCompletion)
728 return TRUE;
730 } else if (arg->i == HideCompletion)
731 return TRUE;
732 if (!widgets) {
733 prefix = g_strdup_printf(str);
734 widgets = malloc(sizeof(GtkWidget*) * listlen);
735 suggestions = malloc(sizeof(char*) * listlen);
736 top_border = gtk_event_box_new();
737 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
738 gdk_color_parse(completioncolor[2], &color);
739 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
740 table = gtk_event_box_new();
741 gdk_color_parse(completionbgcolor[0], &color);
742 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
743 highlight = len > 1;
744 if (strchr(str, ' ') == NULL) {
745 /* command completion */
746 listlen = LENGTH(commands);
747 for (i = 0; i < listlen; i++) {
748 if (commands[i].cmd == NULL)
749 break;
750 cmdlen = strlen(commands[i].cmd);
751 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
752 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
753 if (highlight) {
754 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
755 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
756 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
757 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
759 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
760 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
761 row_eventbox = gtk_event_box_new();
762 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
763 el = gtk_label_new(NULL);
764 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
765 free(s);
766 gtk_label_set_markup(GTK_LABEL(el), markup);
767 g_free(markup);
768 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
769 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
770 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
771 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
772 suggestions[n] = commands[i].cmd;
773 widgets[n++] = row_eventbox;
776 } else {
777 entry = (char *)malloc(512 * sizeof(char));
778 if (entry == NULL) {
779 return FALSE;
781 memset(entry, 0, 512);
782 suggurls = malloc(sizeof(char*) * listlen);
783 if (suggurls == NULL) {
784 return FALSE;
786 spacepos = strcspn(str, " ");
787 searchfor = (str + spacepos + 1);
788 strncpy(command, (str + 1), spacepos - 1);
789 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
790 /* browser settings */
791 listlen = LENGTH(browsersettings);
792 for (i = 0; i < listlen; i++) {
793 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
794 /* match */
795 fill_suggline(suggline, command, browsersettings[i].name);
796 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
797 strncpy(suggurls[n], suggline, 512);
798 suggestions[n] = suggurls[n];
799 row_eventbox = fill_eventbox(suggline);
800 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
801 widgets[n++] = row_eventbox;
805 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
806 /* completion on tags */
807 spacepos = strcspn(str, " ");
808 searchfor = (str + spacepos + 1);
809 elementlist = complete_list(searchfor, 1, elementlist);
810 } else {
811 /* URL completion: bookmarks */
812 elementlist = complete_list(searchfor, 0, elementlist);
813 m = count_list(elementlist);
814 if (m < MAX_LIST_SIZE) {
815 /* URL completion: history */
816 elementlist = complete_list(searchfor, 2, elementlist);
819 elementpointer = elementlist;
820 while (elementpointer != NULL) {
821 fill_suggline(suggline, command, elementpointer->element);
822 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
823 strncpy(suggurls[n], suggline, 512);
824 suggestions[n] = suggurls[n];
825 row_eventbox = fill_eventbox(suggline);
826 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
827 widgets[n++] = row_eventbox;
828 elementpointer = elementpointer->next;
829 if (n >= MAX_LIST_SIZE)
830 break;
832 free_list(elementlist);
833 if (suggurls != NULL) {
834 free(suggurls);
835 suggurls = NULL;
837 if (entry != NULL) {
838 free(entry);
839 entry = NULL;
842 /* TA: FIXME - this needs rethinking entirely. */
844 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
845 if (widgets_temp == NULL && widgets == NULL) {
846 fprintf(stderr, "Couldn't realloc() widgets\n");
847 exit(1);
849 widgets = widgets_temp;
850 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
851 if (suggestions_temp == NULL && suggestions == NULL) {
852 fprintf(stderr, "Couldn't realloc() suggestions\n");
853 exit(1);
855 suggestions = suggestions_temp;
857 if (!n) {
858 gdk_color_parse(completionbgcolor[1], &color);
859 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
860 el = gtk_label_new(NULL);
861 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
862 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
863 gtk_label_set_markup(GTK_LABEL(el), markup);
864 g_free(markup);
865 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
867 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
868 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
869 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
870 gtk_widget_show_all(GTK_WIDGET(window));
871 if (!n)
872 return TRUE;
873 current = arg->i == DirectionPrev ? n - 1 : 0;
875 if (current != -1) {
876 gdk_color_parse(completionbgcolor[2], &color);
877 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
878 s = g_strconcat(":", suggestions[current], NULL);
879 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
880 g_free(s);
881 } else
882 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
883 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
884 return TRUE;
887 gboolean
888 descend(const Arg *arg) {
889 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
890 int i, len;
891 count = count ? count : 1;
893 if (!source)
894 return TRUE;
895 if (arg->i == Rootdir) {
896 for (i = 0; i < 3; i++) /* get to the third slash */
897 if (!(p = strchr(++p, '/')))
898 return TRUE; /* if we cannot find it quit */
899 } else {
900 len = strlen(source);
901 if (!len) /* if string is empty quit */
902 return TRUE;
903 p = source + len; /* start at the end */
904 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
905 ++count;
906 for (i = 0; i < count; i++)
907 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
908 if (p == source) /* if we reach the first char pointer quit */
909 return TRUE;
910 ++p; /* since we do p-- in the while, we are pointing at
911 the char before the slash, so +1 */
913 len = p - source + 1; /* new length = end - start + 1 */
914 new = malloc(len + 1);
915 memcpy(new, source, len);
916 new[len] = '\0';
917 webkit_web_view_load_uri(webview, new);
918 free(new);
919 return TRUE;
922 gboolean
923 echo(const Arg *arg) {
924 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
926 if (index < Info || index > Error)
927 return TRUE;
929 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
930 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
932 return TRUE;
935 gboolean
936 input(const Arg *arg) {
937 int pos = 0;
938 count = 0;
939 const char *url;
940 int index = Info;
941 Arg a;
943 /* if inputbox hidden, show it again */
944 if (!gtk_widget_get_visible(inputbox))
945 gtk_widget_set_visible(inputbox, TRUE);
947 update_state();
949 /* Set the colour and font back to the default, so that we don't still
950 * maintain a red colour from a warning from an end of search indicator,
951 * etc.
953 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
955 /* to avoid things like :open URL :open URL2 or :open :open URL */
956 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
957 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
958 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
959 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
960 gtk_widget_grab_focus(inputbox);
961 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
963 if (arg->s[0] == '.' || arg->s[0] == ',') {
964 mode = ModeHints;
965 memset(followTarget, 0, 8);
966 strncpy(followTarget, "current", 8);
967 a.i = Silent;
968 switch (arg->s[0]) {
969 case '.':
970 a.s = g_strdup("hints.createHints('', 'f');");
971 break;
973 case ',':
974 a.s = g_strdup("hints.createHints('', 'F');");
975 break;
977 count = 0;
978 script(&a);
981 return TRUE;
984 gboolean
985 navigate(const Arg *arg) {
986 if (arg->i & NavigationForwardBack)
987 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
988 else if (arg->i & NavigationReloadActions)
989 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
990 else
991 webkit_web_view_stop_loading(webview);
992 return TRUE;
995 gboolean
996 number(const Arg *arg) {
997 const char *source = webkit_web_view_get_uri(webview);
998 char *uri, *p, *new;
999 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1001 if (!source)
1002 return TRUE;
1003 uri = g_strdup_printf(source); /* copy string */
1004 p =& uri[0];
1005 while(*p != '\0') /* goto the end of the string */
1006 ++p;
1007 --p;
1008 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1009 --p;
1010 if (*(++p) == '\0') { /* if no numbers were found abort */
1011 free(uri);
1012 return TRUE;
1014 number = atoi(p) + diff; /* apply diff on number */
1015 *p = '\0';
1016 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1017 webkit_web_view_load_uri(webview, new);
1018 g_free(new);
1019 free(uri);
1020 return TRUE;
1023 gboolean
1024 open_arg(const Arg *arg) {
1025 char *argv[6];
1026 char *s = arg->s, *p = NULL, *new;
1027 Arg a = { .i = NavigationReload };
1028 int len;
1029 char *search_uri, *search_term;
1031 if (embed) {
1032 argv[0] = *args;
1033 argv[1] = "-e";
1034 argv[2] = winid;
1035 argv[3] = arg->s;
1036 argv[4] = NULL;
1037 } else {
1038 argv[0] = *args;
1039 argv[1] = arg->s;
1040 argv[2] = NULL;
1043 if (!arg->s)
1044 navigate(&a);
1045 else if (arg->i == TargetCurrent) {
1046 while(*s == ' ') /* strip leading whitespace */
1047 ++s;
1048 p = (s + strlen(s) - 1);
1049 while(*p == ' ') /* strip trailing whitespace */
1050 --p;
1051 *(p + 1) = '\0';
1052 len = strlen(s);
1053 new = NULL, p = strchr(s, ' ');
1054 if (p) { /* check for search engines */
1055 *p = '\0';
1056 search_uri = find_uri_for_searchengine(s);
1057 if (search_uri != NULL) {
1058 search_term = soup_uri_encode(p+1, "&");
1059 new = g_strdup_printf(search_uri, search_term);
1060 g_free(search_term);
1062 *p = ' ';
1064 if (!new) {
1065 if (len > 3 && strstr(s, "://")) { /* valid url? */
1066 p = new = g_malloc(len + 1);
1067 while(*s != '\0') { /* strip whitespaces */
1068 if (*s != ' ')
1069 *(p++) = *s;
1070 ++s;
1072 *p = '\0';
1073 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1074 new = g_malloc(sizeof("file://") + len);
1075 strcpy(new, "file://");
1076 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1077 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1078 search_uri = find_uri_for_searchengine(defaultsearch);
1079 if (search_uri != NULL) {
1080 search_term = soup_uri_encode(s, "&");
1081 new = g_strdup_printf(search_uri, search_term);
1082 g_free(search_term);
1084 } else { /* prepend "http://" */
1085 new = g_malloc(sizeof("http://") + len);
1086 strcpy(new, "http://");
1087 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1090 webkit_web_view_load_uri(webview, new);
1091 g_free(new);
1092 } else
1093 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1094 return TRUE;
1097 gboolean
1098 open_remembered(const Arg *arg)
1100 Arg a = {arg->i, rememberedURI};
1102 if (strcmp(rememberedURI, "")) {
1103 open_arg(&a);
1105 return TRUE;
1108 gboolean
1109 yank(const Arg *arg) {
1110 const char *url, *feedback;
1112 if (arg->i & SourceURL) {
1113 url = webkit_web_view_get_uri(webview);
1114 if (!url)
1115 return TRUE;
1116 feedback = g_strconcat("Yanked ", url, NULL);
1117 give_feedback(feedback);
1118 if (arg->i & ClipboardPrimary)
1119 gtk_clipboard_set_text(clipboards[0], url, -1);
1120 if (arg->i & ClipboardGTK)
1121 gtk_clipboard_set_text(clipboards[1], url, -1);
1122 } else
1123 webkit_web_view_copy_clipboard(webview);
1124 return TRUE;
1127 gboolean
1128 paste(const Arg *arg) {
1129 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1131 /* If we're over a link, open it in a new target. */
1132 if (strlen(rememberedURI) > 0) {
1133 Arg new_target = { .i = TargetNew, .s = arg->s };
1134 open_arg(&new_target);
1135 return TRUE;
1138 if (arg->i & ClipboardPrimary)
1139 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1140 if (!a.s && arg->i & ClipboardGTK)
1141 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1142 if (a.s)
1143 open_arg(&a);
1144 return TRUE;
1147 gboolean
1148 quit(const Arg *arg) {
1149 FILE *f;
1150 const char *filename;
1151 const char *uri = webkit_web_view_get_uri(webview);
1152 if (uri != NULL) {
1153 /* write last URL into status file for recreation with "u" */
1154 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1155 f = fopen(filename, "w");
1156 if (f != NULL) {
1157 fprintf(f, "%s", uri);
1158 fclose(f);
1161 gtk_main_quit();
1162 return TRUE;
1165 gboolean
1166 revive(const Arg *arg) {
1167 FILE *f;
1168 const char *filename;
1169 char buffer[512] = "";
1170 Arg a = { .i = TargetNew, .s = NULL };
1171 /* get the URL of the window which has been closed last */
1172 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1173 f = fopen(filename, "r");
1174 if (f != NULL) {
1175 fgets(buffer, 512, f);
1176 fclose(f);
1178 if (strlen(buffer) > 0) {
1179 a.s = buffer;
1180 open_arg(&a);
1181 return TRUE;
1183 return FALSE;
1186 static
1187 gboolean print_frame(const Arg *arg)
1189 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1190 webkit_web_frame_print (frame);
1191 return TRUE;
1194 gboolean
1195 search(const Arg *arg) {
1196 count = count ? count : 1;
1197 gboolean success, direction = arg->i & DirectionPrev;
1198 Arg a;
1200 if (arg->s) {
1201 free(search_handle);
1202 search_handle = g_strdup_printf(arg->s);
1204 if (!search_handle)
1205 return TRUE;
1206 if (arg->i & DirectionAbsolute)
1207 search_direction = direction;
1208 else
1209 direction ^= search_direction;
1210 do {
1211 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1212 if (!success) {
1213 if (arg->i & Wrapping) {
1214 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1215 if (success) {
1216 a.i = Warning;
1217 a.s = g_strdup_printf("search hit %s, continuing at %s",
1218 direction ? "BOTTOM" : "TOP",
1219 direction ? "TOP" : "BOTTOM");
1220 echo(&a);
1221 g_free(a.s);
1222 } else
1223 break;
1224 } else
1225 break;
1227 } while(--count);
1228 if (!success) {
1229 a.i = Error;
1230 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1231 echo(&a);
1232 g_free(a.s);
1234 return TRUE;
1237 gboolean
1238 set(const Arg *arg) {
1239 Arg a = { .i = Info | NoAutoHide };
1241 switch (arg->i) {
1242 case ModeNormal:
1243 if (search_handle) {
1244 search_handle = NULL;
1245 webkit_web_view_unmark_text_matches(webview);
1247 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1248 gtk_widget_grab_focus(GTK_WIDGET(webview));
1249 break;
1250 case ModePassThrough:
1251 a.s = g_strdup("-- PASS THROUGH --");
1252 echo(&a);
1253 g_free(a.s);
1254 break;
1255 case ModeSendKey:
1256 a.s = g_strdup("-- PASS TROUGH (next) --");
1257 echo(&a);
1258 g_free(a.s);
1259 break;
1260 case ModeInsert: /* should not be called manually but automatically */
1261 a.s = g_strdup("-- INSERT --");
1262 echo(&a);
1263 g_free(a.s);
1264 break;
1265 default:
1266 return TRUE;
1268 mode = arg->i;
1269 return TRUE;
1272 gchar*
1273 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1274 JSStringRef string_ref;
1275 gchar *string;
1276 size_t length;
1278 string_ref = JSValueToStringCopy(context, ref, NULL);
1279 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1280 string = g_new(gchar, length);
1281 JSStringGetUTF8CString(string_ref, string, length);
1282 JSStringRelease(string_ref);
1283 return string;
1286 void
1287 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1288 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1289 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1290 JSStringRef str;
1291 JSValueRef val, exception;
1293 str = JSStringCreateWithUTF8CString(script);
1294 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1295 JSStringRelease(str);
1296 if (!val)
1297 *message = jsapi_ref_to_string(context, exception);
1298 else
1299 *value = jsapi_ref_to_string(context, val);
1302 gboolean
1303 quickmark(const Arg *a) {
1304 int i, b;
1305 b = atoi(a->s);
1306 char *fn = g_strdup_printf(QUICKMARK_FILE);
1307 FILE *fp;
1308 fp = fopen(fn, "r");
1309 char buf[100];
1311 if (fp != NULL && b < 10) {
1312 for( i=0; i < b; ++i ) {
1313 if (feof(fp)) {
1314 break;
1316 fgets(buf, 100, fp);
1318 char *ptr = strrchr(buf, '\n');
1319 *ptr = '\0';
1320 Arg x = { .s = buf };
1321 if (strlen(buf))
1322 return open_arg(&x);
1323 else {
1324 x.i = Error;
1325 x.s = g_strdup_printf("Quickmark %d not defined", b);
1326 echo(&x);
1327 g_free(x.s);
1328 return false;
1330 } else { return false; }
1333 gboolean
1334 script(const Arg *arg) {
1335 gchar *value = NULL, *message = NULL;
1336 Arg a;
1338 if (!arg->s) {
1339 set_error("Missing argument.");
1340 return FALSE;
1342 jsapi_evaluate_script(arg->s, &value, &message);
1343 if (message) {
1344 set_error(message);
1345 if (arg->s)
1346 g_free(arg->s);
1347 return FALSE;
1349 if (arg->i != Silent && value) {
1350 a.i = arg->i;
1351 a.s = g_strdup(value);
1352 echo(&a);
1353 g_free(a.s);
1355 /* switch mode according to scripts return value */
1356 if (value) {
1357 if (strncmp(value, "done;", 5) == 0) {
1358 a.i = ModeNormal;
1359 set(&a);
1360 } else if (strncmp(value, "insert;", 7) == 0) {
1361 a.i = ModeInsert;
1362 set(&a);
1365 if (arg->s)
1366 g_free(arg->s);
1367 g_free(value);
1368 return TRUE;
1371 gboolean
1372 scroll(const Arg *arg) {
1373 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1374 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1375 float val = gtk_adjustment_get_value(adjust) / max * 100;
1376 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1378 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1379 if (arg->i & ScrollMove)
1380 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1381 direction * /* direction */
1382 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1383 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1384 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1385 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1386 else
1387 gtk_adjustment_set_value(adjust,
1388 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1389 update_state();
1391 return TRUE;
1394 gboolean
1395 zoom(const Arg *arg) {
1396 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1397 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1398 webkit_web_view_get_zoom_level(webview) +
1399 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1400 (count ? (float)count / 100.0 : 1.0));
1401 return TRUE;
1404 gboolean
1405 fake_key_event(const Arg *a) {
1406 if(!embed) {
1407 return FALSE;
1409 Arg err;
1410 err.i = Error;
1411 Display *xdpy;
1412 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1413 err.s = g_strdup("Couldn't find the XDisplay.");
1414 echo(&err);
1415 g_free(err.s);
1416 return FALSE;
1419 XKeyEvent xk;
1420 xk.display = xdpy;
1421 xk.subwindow = None;
1422 xk.time = CurrentTime;
1423 xk.same_screen = True;
1424 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1425 xk.window = embed;
1426 xk.state = a->i;
1428 if( ! a->s ) {
1429 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1430 echo(&err);
1431 g_free(err.s);
1432 return FALSE;
1435 KeySym keysym;
1436 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1437 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1438 echo(&err);
1439 g_free(err.s);
1440 return FALSE;
1443 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1444 err.s = g_strdup("Couldn't translate keysym to keycode");
1445 echo(&err);
1446 g_free(err.s);
1447 return FALSE;
1450 xk.type = KeyPress;
1451 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1452 err.s = g_strdup("XSendEvent failed");
1453 echo(&err);
1454 g_free(err.s);
1455 return FALSE;
1457 XFlush(xdpy);
1459 return TRUE;
1463 gboolean
1464 commandhistoryfetch(const Arg *arg) {
1465 if (arg->i == DirectionPrev) {
1466 commandpointer--;
1467 if (commandpointer < 0)
1468 commandpointer = maxcommands - 1;
1469 } else {
1470 commandpointer++;
1471 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1472 commandpointer = 0;
1475 if (commandpointer < 0)
1476 return FALSE;
1478 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1479 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1480 return TRUE;
1484 gboolean
1485 bookmark(const Arg *arg) {
1486 FILE *f;
1487 const char *filename;
1488 const char *uri = webkit_web_view_get_uri(webview);
1489 const char *title = webkit_web_view_get_title(webview);
1490 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1491 f = fopen(filename, "a");
1492 if (uri == NULL || strlen(uri) == 0) {
1493 set_error("No URI found to bookmark.");
1494 return FALSE;
1496 if (f != NULL) {
1497 fprintf(f, "%s", uri);
1498 if (title != NULL) {
1499 fprintf(f, "%s", " ");
1500 fprintf(f, "%s", title);
1502 if (arg->s && strlen(arg->s)) {
1503 build_taglist(arg, f);
1505 fprintf(f, "%s", "\n");
1506 fclose(f);
1507 give_feedback( "Bookmark saved" );
1508 return TRUE;
1509 } else {
1510 set_error("Bookmarks file not found.");
1511 return FALSE;
1515 gboolean
1516 history() {
1517 FILE *f;
1518 const char *filename;
1519 const char *uri = webkit_web_view_get_uri(webview);
1520 const char *title = webkit_web_view_get_title(webview);
1521 char *entry, buffer[512], *new;
1522 int n, i = 0;
1523 gboolean finished = FALSE;
1524 if (uri != NULL) {
1525 if (title != NULL) {
1526 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1527 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1528 } else {
1529 entry = malloc((strlen(uri) + 1) * sizeof(char));
1530 memset(entry, 0, strlen(uri) + 1);
1532 if (entry != NULL) {
1533 strncpy(entry, uri, strlen(uri));
1534 if (title != NULL) {
1535 strncat(entry, " ", 1);
1536 strncat(entry, title, strlen(title));
1538 n = strlen(entry);
1539 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1540 f = fopen(filename, "r");
1541 if (f != NULL) {
1542 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1543 if (new != NULL) {
1544 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1545 /* newest entries go on top */
1546 strncpy(new, entry, strlen(entry));
1547 strncat(new, "\n", 1);
1548 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1549 while (finished != TRUE) {
1550 if ((char *)NULL == fgets(buffer, 512, f)) {
1551 /* check if end of file was reached / error occured */
1552 if (!feof(f)) {
1553 break;
1555 /* end of file reached */
1556 finished = TRUE;
1557 continue;
1559 /* compare line (-1 because of newline character) */
1560 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1561 /* if the URI is already in history; we put it on top and skip it here */
1562 strncat(new, buffer, 512);
1563 i++;
1565 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1566 break;
1569 fclose(f);
1571 f = fopen(filename, "w");
1572 if (f != NULL) {
1573 fprintf(f, "%s", new);
1574 fclose(f);
1576 if (new != NULL) {
1577 free(new);
1578 new = NULL;
1582 if (entry != NULL) {
1583 free(entry);
1584 entry = NULL;
1587 return TRUE;
1590 static gboolean
1591 view_source(const Arg * arg) {
1592 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1593 webkit_web_view_set_view_source_mode(webview, !current_mode);
1594 webkit_web_view_reload(webview);
1595 return TRUE;
1598 static gboolean
1599 focus_input(const Arg *arg) {
1600 static Arg a;
1602 a.s = g_strdup("hints.focusInput();");
1603 a.i = Silent;
1604 script(&a);
1605 update_state();
1606 return TRUE;
1609 static gboolean
1610 browser_settings(const Arg *arg) {
1611 char line[255];
1612 if (!arg->s) {
1613 set_error("Missing argument.");
1614 return FALSE;
1616 strncpy(line, arg->s, 254);
1617 if (process_set_line(line))
1618 return TRUE;
1619 else {
1620 set_error("Invalid setting.");
1621 return FALSE;
1625 char *
1626 search_word(int whichword) {
1627 int k = 0;
1628 static char word[240];
1629 char *c = my_pair.line;
1631 while (isspace(*c) && *c)
1632 c++;
1634 switch (whichword) {
1635 case 0:
1636 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1637 word[k++] = *c;
1638 c++;
1640 word[k] = '\0';
1641 strncpy(my_pair.what, word, 20);
1642 break;
1643 case 1:
1644 while (*c && k < 240) {
1645 word[k++] = *c;
1646 c++;
1648 word[k] = '\0';
1649 strncpy(my_pair.value, word, 240);
1650 break;
1653 return c;
1656 static gboolean
1657 process_set_line(char *line) {
1658 char *c;
1659 int listlen, i;
1660 gboolean boolval;
1661 WebKitWebSettings *settings;
1663 settings = webkit_web_view_get_settings(webview);
1664 my_pair.line = line;
1665 c = search_word(0);
1666 if (!strlen(my_pair.what))
1667 return FALSE;
1669 while (isspace(*c) && *c)
1670 c++;
1672 if (*c == ':' || *c == '=')
1673 c++;
1675 my_pair.line = c;
1676 c = search_word(1);
1678 listlen = LENGTH(browsersettings);
1679 for (i = 0; i < listlen; i++) {
1680 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1681 /* mandatory argument not provided */
1682 if (strlen(my_pair.value) == 0)
1683 return FALSE;
1684 /* process qmark? */
1685 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1686 return (process_save_qmark(my_pair.value, webview));
1688 /* interpret boolean values */
1689 if (browsersettings[i].boolval) {
1690 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) {
1691 boolval = TRUE;
1692 } 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) {
1693 boolval = FALSE;
1694 } else {
1695 return FALSE;
1697 } else if (browsersettings[i].colourval) {
1698 /* interpret as hexadecimal colour */
1699 if (!parse_colour(my_pair.value)) {
1700 return FALSE;
1703 if (browsersettings[i].var != NULL) {
1704 /* write value into internal variable */
1705 /*if (browsersettings[i].intval) {
1706 browsersettings[i].var = atoi(my_pair.value);
1707 } else {*/
1708 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
1709 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
1710 /* in this case, \0 will not have been copied */
1711 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
1712 /* in case this string is also used for a webkit setting, make sure it's consistent */
1713 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
1714 give_feedback("String too long; automatically truncated!");
1716 /*}*/
1718 if (strlen(browsersettings[i].webkit) > 0) {
1719 /* activate appropriate webkit setting */
1720 if (browsersettings[i].boolval) {
1721 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1722 } else if (browsersettings[i].intval) {
1723 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1724 } else {
1725 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1727 webkit_web_view_set_settings(webview, settings);
1730 /* process acceptlanguage*/
1731 if (strlen(my_pair.what) == 14 && strncmp("acceptlanguage", my_pair.what, 14) == 0) {
1732 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
1735 /* toggle proxy usage? */
1736 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1737 toggle_proxy(boolval);
1740 /* Toggle scrollbars. */
1741 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1742 toggle_scrollbars(boolval);
1744 /* Toggle widgets */
1745 if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0)
1746 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
1747 if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0)
1748 gtk_widget_set_visible(inputbox, boolval);
1750 /* case sensitivity of completion */
1751 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1752 complete_case_sensitive = boolval;
1754 /* reload page? */
1755 if (browsersettings[i].reload)
1756 webkit_web_view_reload(webview);
1757 return TRUE;
1760 return FALSE;
1763 gboolean
1764 process_line(char *line) {
1765 char *c = line;
1766 int i;
1767 size_t len, length = strlen(line);
1768 gboolean found = FALSE, success = FALSE;
1769 Arg a;
1771 while (isspace(*c))
1772 c++;
1773 /* Ignore blank lines. */
1774 if (c[0] == '\0')
1775 return TRUE;
1776 for (i = 0; i < LENGTH(commands); i++) {
1777 if (commands[i].cmd == NULL)
1778 break;
1779 len = strlen(commands[i].cmd);
1780 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
1781 found = TRUE;
1782 a.i = commands[i].arg.i;
1783 a.s = length > len + 1 ? &c[len + 1] : commands[i].arg.s;
1784 success = commands[i].func(&a);
1785 break;
1788 save_command_history(c);
1789 if (!found) {
1790 a.i = Error;
1791 a.s = g_strdup_printf("Not a browser command: %s", c);
1792 echo(&a);
1793 } else if (!success) {
1794 a.i = Error;
1795 if (error_msg != NULL) {
1796 a.s = g_strdup_printf("%s", error_msg);
1797 g_free(error_msg);
1798 error_msg = NULL;
1799 } else {
1800 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
1802 echo(&a);
1803 g_free(a.s);
1805 return success;
1808 static gboolean
1809 search_tag(const Arg * a) {
1810 FILE *f;
1811 const char *filename;
1812 const char *tag = a->s;
1813 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1814 int t, i, intag, k;
1816 if (!tag) {
1817 /* The user must give us something to load up. */
1818 set_error("Bookmark tag required with this option.");
1819 return FALSE;
1822 if (strlen(tag) > MAXTAGSIZE) {
1823 set_error("Tag too long.");
1824 return FALSE;
1827 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1828 f = fopen(filename, "r");
1829 if (f == NULL) {
1830 set_error("Couldn't open bookmarks file.");
1831 return FALSE;
1833 while (fgets(s, BUFFERSIZE-1, f)) {
1834 intag = 0;
1835 t = strlen(s) - 1;
1836 while (isspace(s[t]))
1837 t--;
1838 if (s[t] != ']') continue;
1839 while (t > 0) {
1840 if (s[t] == ']') {
1841 if (!intag)
1842 intag = t;
1843 else
1844 intag = 0;
1845 } else {
1846 if (s[t] == '[') {
1847 if (intag) {
1848 i = 0;
1849 k = t + 1;
1850 while (k < intag)
1851 foundtag[i++] = s[k++];
1852 foundtag[i] = '\0';
1853 /* foundtag now contains the tag */
1854 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
1855 i = 0;
1856 while (isspace(s[i])) i++;
1857 k = 0;
1858 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
1859 url[k] = '\0';
1860 Arg x = { .i = TargetNew, .s = url };
1861 open_arg(&x);
1864 intag = 0;
1867 t--;
1870 return TRUE;
1873 void
1874 toggle_proxy(gboolean onoff) {
1875 SoupURI *proxy_uri;
1876 char *filename, *new;
1877 int len;
1879 if (onoff == FALSE) {
1880 g_object_set(session, "proxy-uri", NULL, NULL);
1881 give_feedback("Proxy deactivated");
1882 } else {
1883 filename = (char *)g_getenv("http_proxy");
1885 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
1886 * defined.
1888 if (filename == NULL)
1889 filename = (char *)g_getenv("HTTP_PROXY");
1891 if (filename != NULL && 0 < (len = strlen(filename))) {
1892 if (strstr(filename, "://") == NULL) {
1893 /* prepend http:// */
1894 new = g_malloc(sizeof("http://") + len);
1895 strcpy(new, "http://");
1896 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
1897 proxy_uri = soup_uri_new(new);
1898 } else {
1899 proxy_uri = soup_uri_new(filename);
1901 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1902 give_feedback("Proxy activated");
1907 void
1908 toggle_scrollbars(gboolean onoff) {
1909 if (onoff == TRUE) {
1910 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
1911 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
1913 else {
1914 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1915 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1917 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
1919 return;
1922 void
1923 update_url(const char *uri) {
1924 gboolean ssl = g_str_has_prefix(uri, "https://");
1925 GdkColor color;
1926 #ifdef ENABLE_HISTORY_INDICATOR
1927 char before[] = " [";
1928 char after[] = "]";
1929 gboolean back = webkit_web_view_can_go_back(webview);
1930 gboolean fwd = webkit_web_view_can_go_forward(webview);
1932 if (!back && !fwd)
1933 before[0] = after[0] = '\0';
1934 #endif
1935 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped(
1936 #ifdef ENABLE_HISTORY_INDICATOR
1937 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
1938 before, back ? "+" : "", fwd ? "-" : "", after
1939 #else
1940 "<span font=\"%s\">%s</span>", statusfont, uri
1941 #endif
1943 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
1944 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
1945 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
1946 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
1947 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
1950 void
1951 update_state() {
1952 char *markup;
1953 int download_count = g_list_length(activeDownloads);
1954 GString *status = g_string_new("");
1956 /* construct the status line */
1958 /* count, modkey and input buffer */
1959 g_string_append_printf(status, "%.0d", count);
1960 if (current_modkey) g_string_append_c(status, current_modkey);
1962 /* the number of active downloads */
1963 if (activeDownloads) {
1964 g_string_append_printf(status, " %d active %s", download_count,
1965 (download_count == 1) ? "download" : "downloads");
1968 #ifdef ENABLE_WGET_PROGRESS_BAR
1969 /* the progressbar */
1971 int progress = -1;
1972 char progressbar[progressbartick + 1];
1974 if (activeDownloads) {
1975 progress = 0;
1976 GList *ptr;
1978 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
1979 progress += 100 * webkit_download_get_progress(ptr->data);
1982 progress /= download_count;
1984 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
1985 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
1987 progress = webkit_web_view_get_progress(webview) * 100;
1990 if (progress >= 0) {
1991 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
1992 g_string_append_printf(status, " %c%s%c",
1993 progressborderleft, progressbar, progressborderright);
1996 #endif
1998 /* and the current scroll position */
2000 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2001 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2003 if (max == 0)
2004 g_string_append(status, " All");
2005 else if (val == 0)
2006 g_string_append(status, " Top");
2007 else if (val == 100)
2008 g_string_append(status, " Bot");
2009 else
2010 g_string_append_printf(status, " %d%%", val);
2014 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2015 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2017 g_string_free(status, TRUE);
2020 void
2021 setup_modkeys() {
2022 unsigned int i;
2023 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2024 char *ptr = modkeys;
2026 for (i = 0; i < LENGTH(keys); i++)
2027 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2028 *(ptr++) = keys[i].modkey;
2029 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2032 void
2033 setup_gui() {
2034 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2035 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2036 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2037 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2038 if (embed) {
2039 window = GTK_WINDOW(gtk_plug_new(embed));
2040 } else {
2041 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2042 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2044 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2045 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2046 inputbox = gtk_entry_new();
2047 webview = (WebKitWebView*)webkit_web_view_new();
2048 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2049 eventbox = gtk_event_box_new();
2050 status_url = gtk_label_new(NULL);
2051 status_state = gtk_label_new(NULL);
2052 GdkColor bg;
2053 PangoFontDescription *font;
2054 GdkGeometry hints = { 1, 1 };
2055 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2057 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2058 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2059 setup_settings();
2060 gdk_color_parse(statusbgcolor, &bg);
2061 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2062 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2063 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2065 #ifdef DISABLE_SCROLLBAR
2066 viewport = gtk_scrolled_window_new(NULL, NULL);
2067 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2068 #else
2069 /* Ensure we still see scrollbars. */
2070 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2071 #endif
2073 setup_signals();
2074 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2076 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2077 * titlebars, we can still scroll.
2079 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2081 font = pango_font_description_from_string(urlboxfont[0]);
2082 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2083 pango_font_description_free(font);
2084 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2085 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2086 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2087 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2088 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2089 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2090 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2091 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2092 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2093 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2094 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2095 gtk_widget_grab_focus(GTK_WIDGET(webview));
2096 gtk_widget_show_all(GTK_WIDGET(window));
2099 void
2100 setup_settings() {
2101 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2102 SoupURI *proxy_uri;
2103 char *filename, *new;
2104 int len;
2106 session = webkit_get_default_session();
2107 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2108 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2109 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2110 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2111 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2112 filename = g_strdup_printf(USER_STYLESHEET);
2113 filename = g_strdup_printf("file://%s", filename);
2114 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", filename, NULL);
2115 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2116 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2117 webkit_web_view_set_settings(webview, settings);
2119 /* proxy */
2120 if (use_proxy == TRUE) {
2121 filename = (char *)g_getenv("http_proxy");
2122 if (filename != NULL && 0 < (len = strlen(filename))) {
2123 if (strstr(filename, "://") == NULL) {
2124 /* prepend http:// */
2125 new = g_malloc(sizeof("http://") + len);
2126 strcpy(new, "http://");
2127 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2128 proxy_uri = soup_uri_new(new);
2129 } else {
2130 proxy_uri = soup_uri_new(filename);
2132 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2137 void
2138 setup_signals() {
2139 #ifdef ENABLE_COOKIE_SUPPORT
2140 /* Headers. */
2141 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2142 #endif
2143 /* Accept-language header */
2144 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2145 /* window */
2146 g_object_connect(G_OBJECT(window),
2147 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2148 NULL);
2149 /* webview */
2150 g_object_connect(G_OBJECT(webview),
2151 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2152 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2153 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2154 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2155 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2156 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2157 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2158 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2159 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2160 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2161 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2162 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2163 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2164 NULL);
2165 /* webview adjustment */
2166 g_object_connect(G_OBJECT(adjust_v),
2167 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2168 NULL);
2169 /* inputbox */
2170 g_object_connect(G_OBJECT(inputbox),
2171 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2172 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2173 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2174 #ifdef ENABLE_INCREMENTAL_SEARCH
2175 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2176 #endif
2177 NULL);
2178 /* inspector */
2179 g_signal_connect(G_OBJECT(inspector),
2180 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2183 #ifdef ENABLE_COOKIE_SUPPORT
2184 void
2185 setup_cookies()
2187 if (file_cookie_jar)
2188 g_object_unref(file_cookie_jar);
2190 if (session_cookie_jar)
2191 g_object_unref(session_cookie_jar);
2193 session_cookie_jar = soup_cookie_jar_new();
2195 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2197 load_all_cookies();
2199 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2200 G_CALLBACK(update_cookie_jar), NULL);
2202 return;
2205 /* TA: XXX - we should be using this callback for any header-requests we
2206 * receive (hence the name "new_generic_request" -- but for now, its use
2207 * is limited to handling cookies.
2209 void
2210 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2211 SoupMessageHeaders *soup_msg_h;
2212 SoupURI *uri;
2213 const char *cookie_str;
2215 soup_msg_h = soup_msg->request_headers;
2216 soup_message_headers_remove(soup_msg_h, "Cookie");
2217 uri = soup_message_get_uri(soup_msg);
2218 if( (cookie_str = get_cookies(uri)) )
2219 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2221 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2223 return;
2226 const char *
2227 get_cookies(SoupURI *soup_uri) {
2228 const char *cookie_str;
2230 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2232 return cookie_str;
2235 void
2236 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2238 GSList *resp_cookie = NULL;
2239 SoupCookie *cookie;
2241 for(resp_cookie = soup_cookies_from_response(soup_msg);
2242 resp_cookie;
2243 resp_cookie = g_slist_next(resp_cookie))
2245 SoupDate *soup_date;
2246 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2248 if (cookie_timeout && cookie->expires == NULL) {
2249 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2250 soup_cookie_set_expires(cookie, soup_date);
2252 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2255 return;
2258 void
2259 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2261 if (!new) {
2262 /* Nothing to do. */
2263 return;
2266 SoupCookie *copy;
2267 copy = soup_cookie_copy(new);
2269 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2271 return;
2274 void
2275 load_all_cookies(void)
2277 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2279 /* Put them back in the session store. */
2280 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2282 for (; cookies_from_file;
2283 cookies_from_file = cookies_from_file->next)
2285 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2288 soup_cookies_free(cookies_from_file);
2290 return;
2293 #endif
2295 void
2296 mop_up(void) {
2297 /* Free up any nasty globals before exiting. */
2298 #ifdef ENABLE_COOKIE_SUPPORT
2299 if (cookie_store)
2300 g_free(cookie_store);
2301 #endif
2302 return;
2306 main(int argc, char *argv[]) {
2307 static Arg a;
2308 static char url[256] = "";
2309 static gboolean ver = false;
2310 static gboolean configfile_exists = FALSE;
2311 static const char *cfile = NULL;
2312 char *searchengines_file;
2313 static GOptionEntry opts[] = {
2314 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2315 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2316 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2317 { NULL }
2319 static GError *err;
2320 args = argv;
2322 /* command line argument: version */
2323 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2324 g_printerr("can't init gtk: %s\n", err->message);
2325 g_error_free(err);
2326 return EXIT_FAILURE;
2329 if (ver) {
2330 printf("%s\n", INTERNAL_VERSION);
2331 return EXIT_SUCCESS;
2334 if (cfile)
2335 configfile = g_strdup_printf(cfile);
2336 else
2337 configfile = g_strdup_printf(RCFILE);
2339 if (!g_thread_supported())
2340 g_thread_init(NULL);
2342 if (winid)
2343 embed = atoi(winid);
2345 setup_modkeys();
2346 make_keyslist();
2347 setup_gui();
2348 #ifdef ENABLE_COOKIE_SUPPORT
2349 setup_cookies();
2350 #endif
2352 /* Check if the specified file exists. */
2353 /* And only warn the user, if they explicitly asked for a config on the
2354 * command line.
2356 if (!(access(configfile, F_OK) == 0) && cfile) {
2357 char *feedback_str;
2359 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2360 give_feedback(feedback_str);
2361 } else if ((access(configfile, F_OK) == 0))
2362 configfile_exists = true;
2364 /* read config file */
2365 /* But only report errors if we failed, and the file existed. */
2366 if (!read_rcfile(configfile) && configfile_exists) {
2367 a.i = Error;
2368 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2369 echo(&a);
2370 g_free(a.s);
2371 g_free(configfile);
2374 make_searchengines_list(searchengines, LENGTH(searchengines));
2376 /* read searchengines. */
2377 searchengines_file = g_strdup_printf(SEARCHENGINES_STORAGE_FILENAME);
2378 switch (read_searchengines(searchengines_file)) {
2379 case SYNTAX_ERROR:
2380 a.i = Error;
2381 a.s = g_strdup_printf("Syntax error in searchengines file '%s'", searchengines_file);
2382 echo(&a);
2383 break;
2384 case READING_FAILED:
2385 a.i = Error;
2386 a.s = g_strdup_printf("Could not read searchengines file '%s'", searchengines_file);
2387 echo(&a);
2388 break;
2389 default:
2390 break;
2392 g_free(searchengines_file);
2394 /* command line argument: URL */
2395 if (argc > 1) {
2396 strncpy(url, argv[argc - 1], 255);
2397 } else {
2398 strncpy(url, startpage, 255);
2401 a.i = TargetCurrent;
2402 a.s = url;
2403 open_arg(&a);
2404 gtk_main();
2406 mop_up();
2408 return EXIT_SUCCESS;