Reduced z-index to 100000 to work also for other webkit versions.
[vimprobable/e.git] / main.c
blob9f724e4a14c342a5520c89d0800fb6203255bece
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 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);
52 /* functions */
53 static gboolean bookmark(const Arg *arg);
54 static gboolean browser_settings(const Arg *arg);
55 static gboolean commandhistoryfetch(const Arg *arg);
56 static gboolean complete(const Arg *arg);
57 static gboolean descend(const Arg *arg);
58 gboolean echo(const Arg *arg);
59 static gboolean focus_input(const Arg *arg);
60 static gboolean input(const Arg *arg);
61 static gboolean navigate(const Arg *arg);
62 static gboolean number(const Arg *arg);
63 static gboolean open_arg(const Arg *arg);
64 static gboolean open_remembered(const Arg *arg);
65 static gboolean paste(const Arg *arg);
66 static gboolean quickmark(const Arg *arg);
67 static gboolean quit(const Arg *arg);
68 static gboolean revive(const Arg *arg);
69 static gboolean print_frame(const Arg *arg);
70 static gboolean search(const Arg *arg);
71 static gboolean set(const Arg *arg);
72 static gboolean script(const Arg *arg);
73 static gboolean scroll(const Arg *arg);
74 static gboolean search_tag(const Arg *arg);
75 static gboolean yank(const Arg *arg);
76 static gboolean view_source(const Arg * arg);
77 static gboolean zoom(const Arg *arg);
78 static gboolean fake_key_event(const Arg *arg);
80 static void update_url(const char *uri);
81 static void setup_modkeys(void);
82 static void setup_gui(void);
83 static void setup_settings(void);
84 static void setup_signals(void);
85 static void ascii_bar(int total, int state, char *string);
86 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
87 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
88 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
89 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
90 const char *bg_color_str, const char *fg_color_str);
92 static gboolean history(void);
93 static gboolean process_set_line(char *line);
94 void save_command_history(char *line);
95 void toggle_proxy(gboolean onoff);
96 void toggle_scrollbars(gboolean onoff);
98 gboolean process_keypress(GdkEventKey *event);
99 void fill_suggline(char * suggline, const char * command, const char *fill_with);
100 GtkWidget * fill_eventbox(const char * completion_line);
101 static void mop_up(void);
103 #include "main.h"
105 /* variables */
106 static GtkWindow *window;
107 static GtkWidget *viewport;
108 static GtkBox *box;
109 static GtkScrollbar *scroll_h;
110 static GtkScrollbar *scroll_v;
111 static GtkAdjustment *adjust_h;
112 static GtkAdjustment *adjust_v;
113 static GtkWidget *inputbox;
114 static GtkWidget *eventbox;
115 static GtkBox *statusbar;
116 static GtkWidget *status_url;
117 static GtkWidget *status_state;
118 static WebKitWebView *webview;
119 static SoupSession *session;
120 static GtkClipboard *clipboards[2];
122 static char **args;
123 static unsigned int mode = ModeNormal;
124 static unsigned int count = 0;
125 static float zoomstep;
126 static char *modkeys;
127 static char current_modkey;
128 static char *search_handle;
129 static gboolean search_direction;
130 static gboolean echo_active = TRUE;
131 WebKitWebInspector *inspector;
133 static GdkNativeWindow embed = 0;
134 static char *configfile = NULL;
135 static char *winid = NULL;
137 static char rememberedURI[1024] = "";
138 static char followTarget[8] = "";
139 char *error_msg = NULL;
141 GList *activeDownloads;
143 #include "config.h"
144 #include "keymap.h"
146 char commandhistory[COMMANDHISTSIZE][255];
147 int lastcommand = 0;
148 int maxcommands = 0;
149 int commandpointer = 0;
150 KeyList *keylistroot = NULL;
152 /* Cookie support. */
153 #ifdef ENABLE_COOKIE_SUPPORT
154 static SoupCookieJar *session_cookie_jar = NULL;
155 static SoupCookieJar *file_cookie_jar = NULL;
156 static time_t cookie_timeout = 4800;
157 static char *cookie_store;
158 static void setup_cookies(void);
159 static const char *get_cookies(SoupURI *soup_uri);
160 static void load_all_cookies(void);
161 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
162 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
163 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
164 #endif
165 /* callbacks */
166 void
167 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
168 quit(NULL);
171 void
172 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
173 gtk_window_set_title(window, title);
176 void
177 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
178 #ifdef ENABLE_GTK_PROGRESS_BAR
179 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
180 #endif
181 update_state();
184 #ifdef ENABLE_WGET_PROGRESS_BAR
185 void
186 ascii_bar(int total, int state, char *string) {
187 int i;
189 for (i = 0; i < state; i++)
190 string[i] = progressbartickchar;
191 string[i++] = progressbarcurrent;
192 for (; i < total; i++)
193 string[i] = progressbarspacer;
194 string[i] = '\0';
196 #endif
198 void
199 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
200 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
201 const char *uri = webkit_web_view_get_uri(webview);
203 update_url(uri);
204 script(&a);
207 void
208 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
209 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_INPUT_FOCUS) };
211 if (HISTORY_MAX_ENTRIES > 0)
212 history();
213 update_state();
214 script(&a);
217 static gboolean
218 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
219 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
220 if (strlen(rememberedURI) > 0) {
221 a.s = rememberedURI;
223 open_arg(&a);
224 return FALSE;
227 gboolean
228 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
229 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
230 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
231 open_arg(&a);
232 webkit_web_policy_decision_ignore(decision);
233 return TRUE;
236 gboolean
237 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
238 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
239 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
240 webkit_web_policy_decision_download(decision);
241 return TRUE;
242 } else {
243 return FALSE;
247 static WebKitWebView*
248 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
249 gchar* inspector_title;
250 GtkWidget* inspector_window;
251 GtkWidget* inspector_view;
253 /* just enough code to show the inspector - no signal handling etc. */
254 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
255 if (embed) {
256 inspector_window = gtk_plug_new(embed);
257 } else {
258 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
259 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
261 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
262 g_free(inspector_title);
263 inspector_view = webkit_web_view_new();
264 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
265 gtk_widget_show_all(inspector_window);
266 return WEBKIT_WEB_VIEW(inspector_view);
269 gboolean
270 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
271 const gchar *filename;
272 gchar *uri, *path;
273 uint32_t size;
274 Arg a;
276 filename = webkit_download_get_suggested_filename(download);
277 if (filename == NULL || strlen(filename) == 0) {
278 filename = "vimprobable_download";
280 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
281 uri = g_strconcat("file://", path, NULL);
282 webkit_download_set_destination_uri(download, uri);
283 g_free(uri);
284 size = (uint32_t)webkit_download_get_total_size(download);
285 a.i = Info;
286 if (size > 0)
287 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
288 else
289 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
290 echo(&a);
291 g_free(a.s);
292 activeDownloads = g_list_prepend(activeDownloads, download);
293 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
294 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
295 update_state();
296 return TRUE;
299 void
300 download_progress(WebKitDownload *d, GParamSpec *pspec) {
301 Arg a;
302 WebKitDownloadStatus status = webkit_download_get_status(d);
304 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
305 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
306 a.i = Error;
307 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
308 echo(&a);
309 } else {
310 a.i = Info;
311 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
312 echo(&a);
314 g_free(a.s);
315 activeDownloads = g_list_remove(activeDownloads, d);
317 update_state();
321 gboolean
322 process_keypress(GdkEventKey *event) {
323 KeyList *current;
325 current = keylistroot;
326 while (current != NULL) {
327 if (current->Element.mask == CLEAN(event->state)
328 && (current->Element.modkey == current_modkey
329 || (!current->Element.modkey && !current_modkey)
330 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
331 && current->Element.key == event->keyval
332 && current->Element.func)
333 if (current->Element.func(&current->Element.arg)) {
334 current_modkey = count = 0;
335 update_state();
336 return TRUE;
338 current = current->next;
340 return FALSE;
343 gboolean
344 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
345 Arg a = { .i = ModeNormal, .s = NULL };
347 switch (mode) {
348 case ModeNormal:
349 if (CLEAN(event->state) == 0) {
350 if (IS_ESCAPE(event)) {
351 a.i = Info;
352 a.s = g_strdup("");
353 echo(&a);
354 g_free(a.s);
355 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
356 || (event->keyval == GDK_0 && count))) {
357 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
358 update_state();
359 return TRUE;
360 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
361 current_modkey = event->keyval;
362 update_state();
363 return TRUE;
366 /* keybindings */
367 if (process_keypress(event) == TRUE) return TRUE;
369 break;
370 case ModeInsert:
371 if (IS_ESCAPE(event)) {
372 a.i = Silent;
373 a.s = g_strdup("hints.clearFocus();");
374 script(&a);
375 a.i = ModeNormal;
376 return set(&a);
378 case ModePassThrough:
379 if (IS_ESCAPE(event)) {
380 echo(&a);
381 set(&a);
382 return TRUE;
384 break;
385 case ModeSendKey:
386 echo(&a);
387 set(&a);
388 break;
390 return FALSE;
393 void
394 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
395 const char *fg_color_str) {
396 GdkColor fg_color;
397 GdkColor bg_color;
398 PangoFontDescription *font;
400 font = pango_font_description_from_string(font_str);
401 gtk_widget_modify_font(widget, font);
402 pango_font_description_free(font);
404 if (fg_color_str)
405 gdk_color_parse(fg_color_str, &fg_color);
406 if (bg_color_str)
407 gdk_color_parse(bg_color_str, &bg_color);
409 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
410 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
412 return;
415 void
416 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
417 const char *uri = webkit_web_view_get_uri(webview);
419 memset(rememberedURI, 0, 1024);
420 if (link) {
421 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link));
422 strncpy(rememberedURI, link, 1024);
423 } else
424 update_url(uri);
427 gboolean
428 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
429 Arg a;
431 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
432 if (gtk_window_has_toplevel_focus(window)) {
433 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
434 a.i = ModeNormal;
435 return set(&a);
436 } else if (!strcmp(message, "insertmode_on")) {
437 a.i = ModeInsert;
438 return set(&a);
441 return FALSE;
444 void
445 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
446 char *text;
447 guint16 length = gtk_entry_get_text_length(entry);
448 Arg a;
449 gboolean success = FALSE, forward = FALSE;
451 a.i = HideCompletion;
452 complete(&a);
453 if (length == 0)
454 return;
455 text = (char*)gtk_entry_get_text(entry);
456 if (length > 1 && text[0] == ':') {
457 success = process_line((text + 1));
458 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
459 webkit_web_view_unmark_text_matches(webview);
460 #ifdef ENABLE_MATCH_HIGHLITING
461 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
462 webkit_web_view_set_highlight_text_matches(webview, TRUE);
463 #endif
464 count = 0;
465 #ifndef ENABLE_INCREMENTAL_SEARCH
466 a.s =& text[1];
467 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
468 search(&a);
469 #else
470 search_direction = forward;
471 search_handle = g_strdup(&text[1]);
472 #endif
473 } else if (text[0] == '.' || text[0] == ',') {
474 a.i = Silent;
475 a.s = g_strdup_printf("hints.fire();");
476 script(&a);
477 update_state();
478 } else
479 return;
480 if (!echo_active)
481 gtk_entry_set_text(entry, "");
482 gtk_widget_grab_focus(GTK_WIDGET(webview));
485 gboolean
486 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
487 Arg a;
488 int numval;
490 if (mode == ModeHints) {
491 if (event->keyval == GDK_Tab) {
492 a.i = Silent;
493 a.s = g_strdup_printf("hints.focusNextHint();");
494 script(&a);
495 update_state();
496 return TRUE;
498 if (event->keyval == GDK_ISO_Left_Tab) {
499 a.i = Silent;
500 a.s = g_strdup_printf("hints.focusPreviousHint();");
501 script(&a);
502 update_state();
503 return TRUE;
505 if (event->keyval == GDK_Return) {
506 a.i = Silent;
507 a.s = g_strdup_printf("hints.fire();");
508 script(&a);
509 update_state();
510 return TRUE;
513 switch (event->keyval) {
514 case GDK_bracketleft:
515 case GDK_Escape:
516 if (!IS_ESCAPE(event)) break;
517 a.i = HideCompletion;
518 complete(&a);
519 a.i = ModeNormal;
520 return set(&a);
521 break;
522 case GDK_Tab:
523 a.i = DirectionNext;
524 return complete(&a);
525 break;
526 case GDK_Up:
527 a.i = DirectionPrev;
528 return commandhistoryfetch(&a);
529 break;
530 case GDK_Down:
531 a.i = DirectionNext;
532 return commandhistoryfetch(&a);
533 break;
534 case GDK_ISO_Left_Tab:
535 a.i = DirectionPrev;
536 return complete(&a);
537 break;
540 if (mode == ModeHints) {
541 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
542 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
543 (event->keyval == GDK_BackSpace)) {
544 count /= 10;
545 a.i = Silent;
546 a.s = g_strdup_printf("hints.updateHints(%d);", count);
547 script(&a);
548 update_state();
549 return TRUE;
552 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
553 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
554 /* allow a zero as non-first number */
555 count = (count ? count * 10 : 0) + numval;
556 a.i = Silent;
557 a.s = g_strdup_printf("hints.updateHints(%d);", count);
558 script(&a);
559 update_state();
560 return TRUE;
564 return FALSE;
567 gboolean
568 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
569 int i;
570 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
571 /* handle mouse click events */
572 for (i = 0; i < LENGTH(mouse); i++) {
573 if (mouse[i].mask == CLEAN(event->button.state)
574 && (mouse[i].modkey == current_modkey
575 || (!mouse[i].modkey && !current_modkey)
576 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
577 && mouse[i].button == event->button.button
578 && mouse[i].func) {
579 if (mouse[i].func(&mouse[i].arg)) {
580 current_modkey = count = 0;
581 update_state();
582 return TRUE;
587 return FALSE;
590 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
591 Arg a;
592 guint16 length = gtk_entry_get_text_length(entry);
594 if (!length) {
595 a.i = HideCompletion;
596 complete(&a);
597 a.i = ModeNormal;
598 return set(&a);
600 return FALSE;
603 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
604 Arg a;
605 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
606 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
607 gboolean forward = FALSE;
609 /* Update incremental search if the user changes the search text.
611 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
612 * from the user. But if the entry is focused and the text is set
613 * through gtk_entry_set_text() in some asyncrounous operation,
614 * I would consider that a bug.
617 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
618 webkit_web_view_unmark_text_matches(webview);
619 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
620 return TRUE;
621 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
622 (text[0] == '.' || text[0] == ',')) {
623 a.i = Silent;
624 switch (text[0]) {
625 case '.':
626 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
627 break;
629 case ',':
630 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
631 break;
633 count = 0;
634 script(&a);
636 return TRUE;
637 } else if (length == 0 && followTarget[0]) {
638 mode = ModeNormal;
639 a.i = Silent;
640 a.s = g_strdup("hints.clearHints();");
641 script(&a);
642 count = 0;
643 update_state();
646 return FALSE;
649 /* funcs here */
651 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
652 memset(suggline, 0, 512);
653 strncpy(suggline, command, 512);
654 strncat(suggline, " ", 1);
655 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
658 GtkWidget * fill_eventbox(const char * completion_line) {
659 GtkBox * row;
660 GtkWidget *row_eventbox, *el;
661 GdkColor color;
662 char * markup;
664 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
665 row_eventbox = gtk_event_box_new();
666 gdk_color_parse(completionbgcolor[0], &color);
667 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
668 el = gtk_label_new(NULL);
669 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
670 g_markup_escape_text(completion_line, strlen(completion_line)), "</span>", NULL);
671 gtk_label_set_markup(GTK_LABEL(el), markup);
672 g_free(markup);
673 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
674 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
675 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
676 return row_eventbox;
679 gboolean
680 complete(const Arg *arg) {
681 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
682 size_t listlen, len, cmdlen;
683 int i, spacepos;
684 Listelement *elementlist = NULL, *elementpointer;
685 gboolean highlight = FALSE;
686 GtkBox *row;
687 GtkWidget *row_eventbox, *el;
688 GtkBox *_table;
689 GdkColor color;
690 static GtkWidget *table, *top_border;
691 static char *prefix;
692 static char **suggestions;
693 static GtkWidget **widgets;
694 static int n = 0, m, current = -1;
696 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
697 len = strlen(str);
699 /* Get the length of the list of commands for completion. We need this to
700 * malloc/realloc correctly.
702 listlen = LENGTH(commands);
704 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
705 return TRUE;
706 if (prefix) {
707 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
708 gdk_color_parse(completionbgcolor[0], &color);
709 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
710 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
711 if ((arg->i == DirectionNext && current == 0)
712 || (arg->i == DirectionPrev && current == n - 1))
713 current = -1;
714 } else {
715 free(widgets);
716 free(suggestions);
717 free(prefix);
718 gtk_widget_destroy(GTK_WIDGET(table));
719 gtk_widget_destroy(GTK_WIDGET(top_border));
720 table = NULL;
721 widgets = NULL;
722 suggestions = NULL;
723 prefix = NULL;
724 n = 0;
725 current = -1;
726 if (arg->i == HideCompletion)
727 return TRUE;
729 } else if (arg->i == HideCompletion)
730 return TRUE;
731 if (!widgets) {
732 prefix = g_strdup_printf(str);
733 widgets = malloc(sizeof(GtkWidget*) * listlen);
734 suggestions = malloc(sizeof(char*) * listlen);
735 top_border = gtk_event_box_new();
736 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
737 gdk_color_parse(completioncolor[2], &color);
738 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
739 table = gtk_event_box_new();
740 gdk_color_parse(completionbgcolor[0], &color);
741 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
742 highlight = len > 1;
743 if (strchr(str, ' ') == NULL) {
744 /* command completion */
745 listlen = LENGTH(commands);
746 for (i = 0; i < listlen; i++) {
747 if (commands[i].cmd == NULL)
748 break;
749 cmdlen = strlen(commands[i].cmd);
750 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
751 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
752 if (highlight) {
753 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
754 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
755 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
756 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
758 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
759 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
760 row_eventbox = gtk_event_box_new();
761 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
762 el = gtk_label_new(NULL);
763 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
764 free(s);
765 gtk_label_set_markup(GTK_LABEL(el), markup);
766 g_free(markup);
767 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
768 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
769 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
770 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
771 suggestions[n] = commands[i].cmd;
772 widgets[n++] = row_eventbox;
775 } else {
776 entry = (char *)malloc(512 * sizeof(char));
777 if (entry == NULL) {
778 return FALSE;
780 memset(entry, 0, 512);
781 suggurls = malloc(sizeof(char*) * listlen);
782 if (suggurls == NULL) {
783 return FALSE;
785 spacepos = strcspn(str, " ");
786 searchfor = (str + spacepos + 1);
787 strncpy(command, (str + 1), spacepos - 1);
788 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
789 /* browser settings */
790 listlen = LENGTH(browsersettings);
791 for (i = 0; i < listlen; i++) {
792 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
793 /* match */
794 fill_suggline(suggline, command, browsersettings[i].name);
795 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
796 strncpy(suggurls[n], suggline, 512);
797 suggestions[n] = suggurls[n];
798 row_eventbox = fill_eventbox(suggline);
799 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
800 widgets[n++] = row_eventbox;
804 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
805 /* completion on tags */
806 spacepos = strcspn(str, " ");
807 searchfor = (str + spacepos + 1);
808 elementlist = complete_list(searchfor, 1, elementlist);
809 } else {
810 /* URL completion: bookmarks */
811 elementlist = complete_list(searchfor, 0, elementlist);
812 m = count_list(elementlist);
813 if (m < MAX_LIST_SIZE) {
814 /* URL completion: history */
815 elementlist = complete_list(searchfor, 2, elementlist);
818 elementpointer = elementlist;
819 while (elementpointer != NULL) {
820 fill_suggline(suggline, command, elementpointer->element);
821 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
822 strncpy(suggurls[n], suggline, 512);
823 suggestions[n] = suggurls[n];
824 row_eventbox = fill_eventbox(suggline);
825 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
826 widgets[n++] = row_eventbox;
827 elementpointer = elementpointer->next;
828 if (n >= MAX_LIST_SIZE)
829 break;
831 free_list(elementlist);
832 if (suggurls != NULL) {
833 free(suggurls);
834 suggurls = NULL;
836 if (entry != NULL) {
837 free(entry);
838 entry = NULL;
841 /* TA: FIXME - this needs rethinking entirely. */
843 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
844 if (widgets_temp == NULL && widgets == NULL) {
845 fprintf(stderr, "Couldn't realloc() widgets\n");
846 exit(1);
848 widgets = widgets_temp;
849 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
850 if (suggestions_temp == NULL && suggestions == NULL) {
851 fprintf(stderr, "Couldn't realloc() suggestions\n");
852 exit(1);
854 suggestions = suggestions_temp;
856 if (!n) {
857 gdk_color_parse(completionbgcolor[1], &color);
858 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
859 el = gtk_label_new(NULL);
860 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
861 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
862 gtk_label_set_markup(GTK_LABEL(el), markup);
863 g_free(markup);
864 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
866 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
867 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
868 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
869 gtk_widget_show_all(GTK_WIDGET(window));
870 if (!n)
871 return TRUE;
872 current = arg->i == DirectionPrev ? n - 1 : 0;
874 if (current != -1) {
875 gdk_color_parse(completionbgcolor[2], &color);
876 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
877 s = g_strconcat(":", suggestions[current], NULL);
878 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
879 g_free(s);
880 } else
881 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
882 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
883 return TRUE;
886 gboolean
887 descend(const Arg *arg) {
888 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
889 int i, len;
890 count = count ? count : 1;
892 if (!source)
893 return TRUE;
894 if (arg->i == Rootdir) {
895 for (i = 0; i < 3; i++) /* get to the third slash */
896 if (!(p = strchr(++p, '/')))
897 return TRUE; /* if we cannot find it quit */
898 } else {
899 len = strlen(source);
900 if (!len) /* if string is empty quit */
901 return TRUE;
902 p = source + len; /* start at the end */
903 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
904 ++count;
905 for (i = 0; i < count; i++)
906 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
907 if (p == source) /* if we reach the first char pointer quit */
908 return TRUE;
909 ++p; /* since we do p-- in the while, we are pointing at
910 the char before the slash, so +1 */
912 len = p - source + 1; /* new length = end - start + 1 */
913 new = malloc(len + 1);
914 memcpy(new, source, len);
915 new[len] = '\0';
916 webkit_web_view_load_uri(webview, new);
917 free(new);
918 return TRUE;
921 gboolean
922 echo(const Arg *arg) {
923 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
925 if (index < Info || index > Error)
926 return TRUE;
928 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
929 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
931 return TRUE;
934 gboolean
935 input(const Arg *arg) {
936 int pos = 0;
937 count = 0;
938 const char *url;
939 int index = Info;
940 Arg a;
942 /* if inputbox hidden, show it again */
943 if (!gtk_widget_get_visible(inputbox))
944 gtk_widget_set_visible(inputbox, TRUE);
946 update_state();
948 /* Set the colour and font back to the default, so that we don't still
949 * maintain a red colour from a warning from an end of search indicator,
950 * etc.
952 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
954 if (arg->s[0] == '.' || arg->s[0] == ',') {
955 mode = ModeHints;
956 memset(followTarget, 0, 8);
957 strncpy(followTarget, "current", 8);
958 a.i = Silent;
959 switch (arg->s[0]) {
960 case '.':
961 a.s = g_strdup("hints.createHints('', 'f');");
962 break;
964 case ',':
965 a.s = g_strdup("hints.createHints('', 'F');");
966 break;
968 count = 0;
969 script(&a);
972 /* to avoid things like :open URL :open URL2 or :open :open URL */
973 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
974 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
975 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
976 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
977 gtk_widget_grab_focus(inputbox);
978 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
980 return TRUE;
983 gboolean
984 navigate(const Arg *arg) {
985 if (arg->i & NavigationForwardBack)
986 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
987 else if (arg->i & NavigationReloadActions)
988 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
989 else
990 webkit_web_view_stop_loading(webview);
991 return TRUE;
994 gboolean
995 number(const Arg *arg) {
996 const char *source = webkit_web_view_get_uri(webview);
997 char *uri, *p, *new;
998 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1000 if (!source)
1001 return TRUE;
1002 uri = g_strdup_printf(source); /* copy string */
1003 p =& uri[0];
1004 while(*p != '\0') /* goto the end of the string */
1005 ++p;
1006 --p;
1007 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1008 --p;
1009 if (*(++p) == '\0') { /* if no numbers were found abort */
1010 free(uri);
1011 return TRUE;
1013 number = atoi(p) + diff; /* apply diff on number */
1014 *p = '\0';
1015 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1016 webkit_web_view_load_uri(webview, new);
1017 g_free(new);
1018 free(uri);
1019 return TRUE;
1022 gboolean
1023 open_arg(const Arg *arg) {
1024 char *argv[6];
1025 char *s = arg->s, *p = NULL, *new;
1026 Arg a = { .i = NavigationReload };
1027 int len;
1028 char *search_uri, *search_term;
1030 if (embed) {
1031 argv[0] = *args;
1032 argv[1] = "-e";
1033 argv[2] = winid;
1034 argv[3] = arg->s;
1035 argv[4] = NULL;
1036 } else {
1037 argv[0] = *args;
1038 argv[1] = arg->s;
1039 argv[2] = NULL;
1042 if (!arg->s)
1043 navigate(&a);
1044 else if (arg->i == TargetCurrent) {
1045 while(*s == ' ') /* strip leading whitespace */
1046 ++s;
1047 p = (s + strlen(s) - 1);
1048 while(*p == ' ') /* strip trailing whitespace */
1049 --p;
1050 *(p + 1) = '\0';
1051 len = strlen(s);
1052 new = NULL, p = strchr(s, ' ');
1053 if (p) { /* check for search engines */
1054 *p = '\0';
1055 search_uri = find_uri_for_searchengine(s);
1056 if (search_uri != NULL) {
1057 search_term = soup_uri_encode(p+1, "&");
1058 new = g_strdup_printf(search_uri, search_term);
1059 g_free(search_term);
1061 *p = ' ';
1063 if (!new) {
1064 if (len > 3 && strstr(s, "://")) { /* valid url? */
1065 p = new = g_malloc(len + 1);
1066 while(*s != '\0') { /* strip whitespaces */
1067 if (*s != ' ')
1068 *(p++) = *s;
1069 ++s;
1071 *p = '\0';
1072 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1073 new = g_malloc(sizeof("file://") + len);
1074 strcpy(new, "file://");
1075 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1076 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1077 search_uri = find_uri_for_searchengine(defaultsearch);
1078 if (search_uri != NULL) {
1079 search_term = soup_uri_encode(s, "&");
1080 new = g_strdup_printf(search_uri, search_term);
1081 g_free(search_term);
1083 } else { /* prepend "http://" */
1084 new = g_malloc(sizeof("http://") + len);
1085 strcpy(new, "http://");
1086 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1089 webkit_web_view_load_uri(webview, new);
1090 g_free(new);
1091 } else
1092 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1093 return TRUE;
1096 gboolean
1097 open_remembered(const Arg *arg)
1099 Arg a = {arg->i, rememberedURI};
1101 if (strcmp(rememberedURI, "")) {
1102 open_arg(&a);
1104 return TRUE;
1107 gboolean
1108 yank(const Arg *arg) {
1109 const char *url, *feedback;
1111 if (arg->i & SourceURL) {
1112 url = webkit_web_view_get_uri(webview);
1113 if (!url)
1114 return TRUE;
1115 feedback = g_strconcat("Yanked ", url, NULL);
1116 give_feedback(feedback);
1117 if (arg->i & ClipboardPrimary)
1118 gtk_clipboard_set_text(clipboards[0], url, -1);
1119 if (arg->i & ClipboardGTK)
1120 gtk_clipboard_set_text(clipboards[1], url, -1);
1121 } else
1122 webkit_web_view_copy_clipboard(webview);
1123 return TRUE;
1126 gboolean
1127 paste(const Arg *arg) {
1128 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1130 /* If we're over a link, open it in a new target. */
1131 if (strlen(rememberedURI) > 0) {
1132 Arg new_target = { .i = TargetNew, .s = arg->s };
1133 open_arg(&new_target);
1134 return TRUE;
1137 if (arg->i & ClipboardPrimary)
1138 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1139 if (!a.s && arg->i & ClipboardGTK)
1140 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1141 if (a.s)
1142 open_arg(&a);
1143 return TRUE;
1146 gboolean
1147 quit(const Arg *arg) {
1148 FILE *f;
1149 const char *filename;
1150 const char *uri = webkit_web_view_get_uri(webview);
1151 if (uri != NULL) {
1152 /* write last URL into status file for recreation with "u" */
1153 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1154 f = fopen(filename, "w");
1155 if (f != NULL) {
1156 fprintf(f, "%s", uri);
1157 fclose(f);
1160 gtk_main_quit();
1161 return TRUE;
1164 gboolean
1165 revive(const Arg *arg) {
1166 FILE *f;
1167 const char *filename;
1168 char buffer[512] = "";
1169 Arg a = { .i = TargetNew, .s = NULL };
1170 /* get the URL of the window which has been closed last */
1171 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1172 f = fopen(filename, "r");
1173 if (f != NULL) {
1174 fgets(buffer, 512, f);
1175 fclose(f);
1177 if (strlen(buffer) > 0) {
1178 a.s = buffer;
1179 open_arg(&a);
1180 return TRUE;
1182 return FALSE;
1185 static
1186 gboolean print_frame(const Arg *arg)
1188 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1189 webkit_web_frame_print (frame);
1190 return TRUE;
1193 gboolean
1194 search(const Arg *arg) {
1195 count = count ? count : 1;
1196 gboolean success, direction = arg->i & DirectionPrev;
1197 Arg a;
1199 if (arg->s) {
1200 free(search_handle);
1201 search_handle = g_strdup_printf(arg->s);
1203 if (!search_handle)
1204 return TRUE;
1205 if (arg->i & DirectionAbsolute)
1206 search_direction = direction;
1207 else
1208 direction ^= search_direction;
1209 do {
1210 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1211 if (!success) {
1212 if (arg->i & Wrapping) {
1213 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1214 if (success) {
1215 a.i = Warning;
1216 a.s = g_strdup_printf("search hit %s, continuing at %s",
1217 direction ? "BOTTOM" : "TOP",
1218 direction ? "TOP" : "BOTTOM");
1219 echo(&a);
1220 g_free(a.s);
1221 } else
1222 break;
1223 } else
1224 break;
1226 } while(--count);
1227 if (!success) {
1228 a.i = Error;
1229 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1230 echo(&a);
1231 g_free(a.s);
1233 return TRUE;
1236 gboolean
1237 set(const Arg *arg) {
1238 Arg a = { .i = Info | NoAutoHide };
1240 switch (arg->i) {
1241 case ModeNormal:
1242 if (search_handle) {
1243 search_handle = NULL;
1244 webkit_web_view_unmark_text_matches(webview);
1246 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1247 gtk_widget_grab_focus(GTK_WIDGET(webview));
1248 break;
1249 case ModePassThrough:
1250 a.s = g_strdup("-- PASS THROUGH --");
1251 echo(&a);
1252 g_free(a.s);
1253 break;
1254 case ModeSendKey:
1255 a.s = g_strdup("-- PASS TROUGH (next) --");
1256 echo(&a);
1257 g_free(a.s);
1258 break;
1259 case ModeInsert: /* should not be called manually but automatically */
1260 a.s = g_strdup("-- INSERT --");
1261 echo(&a);
1262 g_free(a.s);
1263 break;
1264 default:
1265 return TRUE;
1267 mode = arg->i;
1268 return TRUE;
1271 gchar*
1272 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1273 JSStringRef string_ref;
1274 gchar *string;
1275 size_t length;
1277 string_ref = JSValueToStringCopy(context, ref, NULL);
1278 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1279 string = g_new(gchar, length);
1280 JSStringGetUTF8CString(string_ref, string, length);
1281 JSStringRelease(string_ref);
1282 return string;
1285 void
1286 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1287 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1288 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1289 JSStringRef str;
1290 JSValueRef val, exception;
1292 str = JSStringCreateWithUTF8CString(script);
1293 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1294 JSStringRelease(str);
1295 if (!val)
1296 *message = jsapi_ref_to_string(context, exception);
1297 else
1298 *value = jsapi_ref_to_string(context, val);
1301 gboolean
1302 quickmark(const Arg *a) {
1303 int i, b;
1304 b = atoi(a->s);
1305 char *fn = g_strdup_printf(QUICKMARK_FILE);
1306 FILE *fp;
1307 fp = fopen(fn, "r");
1308 char buf[100];
1310 if (fp != NULL && b < 10) {
1311 for( i=0; i < b; ++i ) {
1312 if (feof(fp)) {
1313 break;
1315 fgets(buf, 100, fp);
1317 char *ptr = strrchr(buf, '\n');
1318 *ptr = '\0';
1319 Arg x = { .s = buf };
1320 if (strlen(buf))
1321 return open_arg(&x);
1322 else {
1323 x.i = Error;
1324 x.s = g_strdup_printf("Quickmark %d not defined", b);
1325 echo(&x);
1326 g_free(x.s);
1327 return false;
1329 } else { return false; }
1332 gboolean
1333 script(const Arg *arg) {
1334 gchar *value = NULL, *message = NULL;
1335 Arg a;
1337 if (!arg->s) {
1338 set_error("Missing argument.");
1339 return FALSE;
1341 jsapi_evaluate_script(arg->s, &value, &message);
1342 if (message) {
1343 set_error(message);
1344 if (arg->s)
1345 g_free(arg->s);
1346 return FALSE;
1348 if (arg->i != Silent && value) {
1349 a.i = arg->i;
1350 a.s = g_strdup(value);
1351 echo(&a);
1352 g_free(a.s);
1354 /* switch mode according to scripts return value */
1355 if (value) {
1356 if (strncmp(value, "done;", 5) == 0) {
1357 a.i = ModeNormal;
1358 set(&a);
1359 } else if (strncmp(value, "insert;", 7) == 0) {
1360 a.i = ModeInsert;
1361 set(&a);
1364 if (arg->s)
1365 g_free(arg->s);
1366 g_free(value);
1367 return TRUE;
1370 gboolean
1371 scroll(const Arg *arg) {
1372 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1373 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1374 float val = gtk_adjustment_get_value(adjust) / max * 100;
1375 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1377 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1378 if (arg->i & ScrollMove)
1379 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1380 direction * /* direction */
1381 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1382 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1383 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1384 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1385 else
1386 gtk_adjustment_set_value(adjust,
1387 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1388 update_state();
1390 return TRUE;
1393 gboolean
1394 zoom(const Arg *arg) {
1395 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1396 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1397 webkit_web_view_get_zoom_level(webview) +
1398 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1399 (count ? (float)count / 100.0 : 1.0));
1400 return TRUE;
1403 gboolean
1404 fake_key_event(const Arg *a) {
1405 if(!embed) {
1406 return FALSE;
1408 Arg err;
1409 err.i = Error;
1410 Display *xdpy;
1411 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1412 err.s = g_strdup("Couldn't find the XDisplay.");
1413 echo(&err);
1414 g_free(err.s);
1415 return FALSE;
1418 XKeyEvent xk;
1419 xk.display = xdpy;
1420 xk.subwindow = None;
1421 xk.time = CurrentTime;
1422 xk.same_screen = True;
1423 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1424 xk.window = embed;
1425 xk.state = a->i;
1427 if( ! a->s ) {
1428 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1429 echo(&err);
1430 g_free(err.s);
1431 return FALSE;
1434 KeySym keysym;
1435 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1436 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1437 echo(&err);
1438 g_free(err.s);
1439 return FALSE;
1442 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1443 err.s = g_strdup("Couldn't translate keysym to keycode");
1444 echo(&err);
1445 g_free(err.s);
1446 return FALSE;
1449 xk.type = KeyPress;
1450 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1451 err.s = g_strdup("XSendEvent failed");
1452 echo(&err);
1453 g_free(err.s);
1454 return FALSE;
1456 XFlush(xdpy);
1458 return TRUE;
1462 gboolean
1463 commandhistoryfetch(const Arg *arg) {
1464 if (arg->i == DirectionPrev) {
1465 commandpointer--;
1466 if (commandpointer < 0)
1467 commandpointer = maxcommands - 1;
1468 } else {
1469 commandpointer++;
1470 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1471 commandpointer = 0;
1474 if (commandpointer < 0)
1475 return FALSE;
1477 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1478 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1479 return TRUE;
1483 gboolean
1484 bookmark(const Arg *arg) {
1485 FILE *f;
1486 const char *filename;
1487 const char *uri = webkit_web_view_get_uri(webview);
1488 const char *title = webkit_web_view_get_title(webview);
1489 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1490 f = fopen(filename, "a");
1491 if (uri == NULL || strlen(uri) == 0) {
1492 set_error("No URI found to bookmark.");
1493 return FALSE;
1495 if (f != NULL) {
1496 fprintf(f, "%s", uri);
1497 if (title != NULL) {
1498 fprintf(f, "%s", " ");
1499 fprintf(f, "%s", title);
1501 if (arg->s && strlen(arg->s)) {
1502 build_taglist(arg, f);
1504 fprintf(f, "%s", "\n");
1505 fclose(f);
1506 give_feedback( "Bookmark saved" );
1507 return TRUE;
1508 } else {
1509 set_error("Bookmarks file not found.");
1510 return FALSE;
1514 gboolean
1515 history() {
1516 FILE *f;
1517 const char *filename;
1518 const char *uri = webkit_web_view_get_uri(webview);
1519 const char *title = webkit_web_view_get_title(webview);
1520 char *entry, buffer[512], *new;
1521 int n, i = 0;
1522 gboolean finished = FALSE;
1523 if (uri != NULL) {
1524 if (title != NULL) {
1525 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1526 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1527 } else {
1528 entry = malloc((strlen(uri) + 1) * sizeof(char));
1529 memset(entry, 0, strlen(uri) + 1);
1531 if (entry != NULL) {
1532 strncpy(entry, uri, strlen(uri));
1533 if (title != NULL) {
1534 strncat(entry, " ", 1);
1535 strncat(entry, title, strlen(title));
1537 n = strlen(entry);
1538 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1539 f = fopen(filename, "r");
1540 if (f != NULL) {
1541 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1542 if (new != NULL) {
1543 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1544 /* newest entries go on top */
1545 strncpy(new, entry, strlen(entry));
1546 strncat(new, "\n", 1);
1547 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1548 while (finished != TRUE) {
1549 if ((char *)NULL == fgets(buffer, 512, f)) {
1550 /* check if end of file was reached / error occured */
1551 if (!feof(f)) {
1552 break;
1554 /* end of file reached */
1555 finished = TRUE;
1556 continue;
1558 /* compare line (-1 because of newline character) */
1559 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1560 /* if the URI is already in history; we put it on top and skip it here */
1561 strncat(new, buffer, 512);
1562 i++;
1564 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1565 break;
1568 fclose(f);
1570 f = fopen(filename, "w");
1571 if (f != NULL) {
1572 fprintf(f, "%s", new);
1573 fclose(f);
1575 if (new != NULL) {
1576 free(new);
1577 new = NULL;
1581 if (entry != NULL) {
1582 free(entry);
1583 entry = NULL;
1586 return TRUE;
1589 static gboolean
1590 view_source(const Arg * arg) {
1591 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1592 webkit_web_view_set_view_source_mode(webview, !current_mode);
1593 webkit_web_view_reload(webview);
1594 return TRUE;
1597 static gboolean
1598 focus_input(const Arg *arg) {
1599 static Arg a;
1601 a.s = g_strdup("hints.focusInput();");
1602 a.i = Silent;
1603 script(&a);
1604 update_state();
1605 return TRUE;
1608 static gboolean
1609 browser_settings(const Arg *arg) {
1610 char line[255];
1611 if (!arg->s) {
1612 set_error("Missing argument.");
1613 return FALSE;
1615 strncpy(line, arg->s, 254);
1616 if (process_set_line(line))
1617 return TRUE;
1618 else {
1619 set_error("Invalid setting.");
1620 return FALSE;
1624 char *
1625 search_word(int whichword) {
1626 int k = 0;
1627 static char word[240];
1628 char *c = my_pair.line;
1630 while (isspace(*c) && *c)
1631 c++;
1633 switch (whichword) {
1634 case 0:
1635 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1636 word[k++] = *c;
1637 c++;
1639 word[k] = '\0';
1640 strncpy(my_pair.what, word, 20);
1641 break;
1642 case 1:
1643 while (*c && k < 240) {
1644 word[k++] = *c;
1645 c++;
1647 word[k] = '\0';
1648 strncpy(my_pair.value, word, 240);
1649 break;
1652 return c;
1655 static gboolean
1656 process_set_line(char *line) {
1657 char *c;
1658 int listlen, i;
1659 gboolean boolval;
1660 WebKitWebSettings *settings;
1662 settings = webkit_web_view_get_settings(webview);
1663 my_pair.line = line;
1664 c = search_word(0);
1665 if (!strlen(my_pair.what))
1666 return FALSE;
1668 while (isspace(*c) && *c)
1669 c++;
1671 if (*c == ':' || *c == '=')
1672 c++;
1674 my_pair.line = c;
1675 c = search_word(1);
1677 listlen = LENGTH(browsersettings);
1678 for (i = 0; i < listlen; i++) {
1679 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1680 /* mandatory argument not provided */
1681 if (strlen(my_pair.value) == 0)
1682 return FALSE;
1683 /* process qmark? */
1684 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1685 return (process_save_qmark(my_pair.value, webview));
1687 /* interpret boolean values */
1688 if (browsersettings[i].boolval) {
1689 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) {
1690 boolval = TRUE;
1691 } 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) {
1692 boolval = FALSE;
1693 } else {
1694 return FALSE;
1696 } else if (browsersettings[i].colourval) {
1697 /* interpret as hexadecimal colour */
1698 if (!parse_colour(my_pair.value)) {
1699 return FALSE;
1702 if (browsersettings[i].var != NULL) {
1703 /* write value into internal variable */
1704 /*if (browsersettings[i].intval) {
1705 browsersettings[i].var = atoi(my_pair.value);
1706 } else {*/
1707 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
1708 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
1709 /* in this case, \0 will not have been copied */
1710 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
1711 /* in case this string is also used for a webkit setting, make sure it's consistent */
1712 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
1713 give_feedback("String too long; automatically truncated!");
1715 /*}*/
1717 if (strlen(browsersettings[i].webkit) > 0) {
1718 /* activate appropriate webkit setting */
1719 if (browsersettings[i].boolval) {
1720 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1721 } else if (browsersettings[i].intval) {
1722 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1723 } else {
1724 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1726 webkit_web_view_set_settings(webview, settings);
1729 /* process acceptlanguage*/
1730 if (strlen(my_pair.what) == 14 && strncmp("acceptlanguage", my_pair.what, 14) == 0) {
1731 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
1734 /* toggle proxy usage? */
1735 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1736 toggle_proxy(boolval);
1739 /* Toggle scrollbars. */
1740 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1741 toggle_scrollbars(boolval);
1743 /* Toggle widgets */
1744 if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0)
1745 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
1746 if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0)
1747 gtk_widget_set_visible(inputbox, boolval);
1749 /* case sensitivity of completion */
1750 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1751 complete_case_sensitive = boolval;
1753 /* reload page? */
1754 if (browsersettings[i].reload)
1755 webkit_web_view_reload(webview);
1756 return TRUE;
1759 return FALSE;
1762 gboolean
1763 process_line(char *line) {
1764 char *c = line;
1765 int i;
1766 size_t len, length = strlen(line);
1767 gboolean found = FALSE, success = FALSE;
1768 Arg a;
1770 while (isspace(*c))
1771 c++;
1772 /* Ignore blank lines. */
1773 if (c[0] == '\0')
1774 return TRUE;
1775 for (i = 0; i < LENGTH(commands); i++) {
1776 if (commands[i].cmd == NULL)
1777 break;
1778 len = strlen(commands[i].cmd);
1779 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
1780 found = TRUE;
1781 a.i = commands[i].arg.i;
1782 a.s = length > len + 1 ? &c[len + 1] : commands[i].arg.s;
1783 success = commands[i].func(&a);
1784 break;
1787 save_command_history(c);
1788 if (!found) {
1789 a.i = Error;
1790 a.s = g_strdup_printf("Not a browser command: %s", c);
1791 echo(&a);
1792 } else if (!success) {
1793 a.i = Error;
1794 if (error_msg != NULL) {
1795 a.s = g_strdup_printf("%s", error_msg);
1796 g_free(error_msg);
1797 error_msg = NULL;
1798 } else {
1799 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
1801 echo(&a);
1802 g_free(a.s);
1804 return success;
1807 static gboolean
1808 search_tag(const Arg * a) {
1809 FILE *f;
1810 const char *filename;
1811 const char *tag = a->s;
1812 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1813 int t, i, intag, k;
1815 if (!tag) {
1816 /* The user must give us something to load up. */
1817 set_error("Bookmark tag required with this option.");
1818 return FALSE;
1821 if (strlen(tag) > MAXTAGSIZE) {
1822 set_error("Tag too long.");
1823 return FALSE;
1826 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1827 f = fopen(filename, "r");
1828 if (f == NULL) {
1829 set_error("Couldn't open bookmarks file.");
1830 return FALSE;
1832 while (fgets(s, BUFFERSIZE-1, f)) {
1833 intag = 0;
1834 t = strlen(s) - 1;
1835 while (isspace(s[t]))
1836 t--;
1837 if (s[t] != ']') continue;
1838 while (t > 0) {
1839 if (s[t] == ']') {
1840 if (!intag)
1841 intag = t;
1842 else
1843 intag = 0;
1844 } else {
1845 if (s[t] == '[') {
1846 if (intag) {
1847 i = 0;
1848 k = t + 1;
1849 while (k < intag)
1850 foundtag[i++] = s[k++];
1851 foundtag[i] = '\0';
1852 /* foundtag now contains the tag */
1853 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
1854 i = 0;
1855 while (isspace(s[i])) i++;
1856 k = 0;
1857 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
1858 url[k] = '\0';
1859 Arg x = { .i = TargetNew, .s = url };
1860 open_arg(&x);
1863 intag = 0;
1866 t--;
1869 return TRUE;
1872 void
1873 toggle_proxy(gboolean onoff) {
1874 SoupURI *proxy_uri;
1875 char *filename, *new;
1876 int len;
1878 if (onoff == FALSE) {
1879 g_object_set(session, "proxy-uri", NULL, NULL);
1880 give_feedback("Proxy deactivated");
1881 } else {
1882 filename = (char *)g_getenv("http_proxy");
1884 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
1885 * defined.
1887 if (filename == NULL)
1888 filename = (char *)g_getenv("HTTP_PROXY");
1890 if (filename != NULL && 0 < (len = strlen(filename))) {
1891 if (strstr(filename, "://") == NULL) {
1892 /* prepend http:// */
1893 new = g_malloc(sizeof("http://") + len);
1894 strcpy(new, "http://");
1895 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
1896 proxy_uri = soup_uri_new(new);
1897 } else {
1898 proxy_uri = soup_uri_new(filename);
1900 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1901 give_feedback("Proxy activated");
1906 void
1907 toggle_scrollbars(gboolean onoff) {
1908 if (onoff == TRUE) {
1909 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
1910 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
1912 else {
1913 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1914 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1916 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
1918 return;
1921 void
1922 update_url(const char *uri) {
1923 gboolean ssl = g_str_has_prefix(uri, "https://");
1924 GdkColor color;
1925 #ifdef ENABLE_HISTORY_INDICATOR
1926 char before[] = " [";
1927 char after[] = "]";
1928 gboolean back = webkit_web_view_can_go_back(webview);
1929 gboolean fwd = webkit_web_view_can_go_forward(webview);
1931 if (!back && !fwd)
1932 before[0] = after[0] = '\0';
1933 #endif
1934 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped(
1935 #ifdef ENABLE_HISTORY_INDICATOR
1936 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
1937 before, back ? "+" : "", fwd ? "-" : "", after
1938 #else
1939 "<span font=\"%s\">%s</span>", statusfont, uri
1940 #endif
1942 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
1943 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
1944 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
1945 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
1946 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
1949 void
1950 update_state() {
1951 char *markup;
1952 int download_count = g_list_length(activeDownloads);
1953 GString *status = g_string_new("");
1955 /* construct the status line */
1957 /* count, modkey and input buffer */
1958 g_string_append_printf(status, "%.0d", count);
1959 if (current_modkey) g_string_append_c(status, current_modkey);
1961 /* the number of active downloads */
1962 if (activeDownloads) {
1963 g_string_append_printf(status, " %d active %s", download_count,
1964 (download_count == 1) ? "download" : "downloads");
1967 #ifdef ENABLE_WGET_PROGRESS_BAR
1968 /* the progressbar */
1970 int progress = -1;
1971 char progressbar[progressbartick + 1];
1973 if (activeDownloads) {
1974 progress = 0;
1975 GList *ptr;
1977 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
1978 progress += 100 * webkit_download_get_progress(ptr->data);
1981 progress /= download_count;
1983 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
1984 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
1986 progress = webkit_web_view_get_progress(webview) * 100;
1989 if (progress >= 0) {
1990 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
1991 g_string_append_printf(status, " %c%s%c",
1992 progressborderleft, progressbar, progressborderright);
1995 #endif
1997 /* and the current scroll position */
1999 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2000 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2002 if (max == 0)
2003 g_string_append(status, " All");
2004 else if (val == 0)
2005 g_string_append(status, " Top");
2006 else if (val == 100)
2007 g_string_append(status, " Bot");
2008 else
2009 g_string_append_printf(status, " %d%%", val);
2013 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2014 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2016 g_string_free(status, TRUE);
2019 void
2020 setup_modkeys() {
2021 unsigned int i;
2022 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2023 char *ptr = modkeys;
2025 for (i = 0; i < LENGTH(keys); i++)
2026 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2027 *(ptr++) = keys[i].modkey;
2028 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2031 void
2032 setup_gui() {
2033 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2034 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2035 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2036 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2037 if (embed) {
2038 window = GTK_WINDOW(gtk_plug_new(embed));
2039 } else {
2040 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2041 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2043 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2044 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2045 inputbox = gtk_entry_new();
2046 webview = (WebKitWebView*)webkit_web_view_new();
2047 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2048 eventbox = gtk_event_box_new();
2049 status_url = gtk_label_new(NULL);
2050 status_state = gtk_label_new(NULL);
2051 GdkColor bg;
2052 PangoFontDescription *font;
2053 GdkGeometry hints = { 1, 1 };
2054 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2056 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2057 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2058 setup_settings();
2059 gdk_color_parse(statusbgcolor, &bg);
2060 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2061 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2062 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2064 #ifdef DISABLE_SCROLLBAR
2065 viewport = gtk_scrolled_window_new(NULL, NULL);
2066 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2067 #else
2068 /* Ensure we still see scrollbars. */
2069 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2070 #endif
2072 setup_signals();
2073 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2075 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2076 * titlebars, we can still scroll.
2078 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2080 font = pango_font_description_from_string(urlboxfont[0]);
2081 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2082 pango_font_description_free(font);
2083 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2084 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2085 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2086 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2087 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2088 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2089 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2090 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2091 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2092 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2093 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2094 gtk_widget_grab_focus(GTK_WIDGET(webview));
2095 gtk_widget_show_all(GTK_WIDGET(window));
2098 void
2099 setup_settings() {
2100 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2101 SoupURI *proxy_uri;
2102 char *filename, *new;
2103 int len;
2105 session = webkit_get_default_session();
2106 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2107 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2108 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2109 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2110 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2111 filename = g_strdup_printf(USER_STYLESHEET);
2112 filename = g_strdup_printf("file://%s", filename);
2113 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", filename, NULL);
2114 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2115 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2116 webkit_web_view_set_settings(webview, settings);
2118 /* proxy */
2119 if (use_proxy == TRUE) {
2120 filename = (char *)g_getenv("http_proxy");
2121 if (filename != NULL && 0 < (len = strlen(filename))) {
2122 if (strstr(filename, "://") == NULL) {
2123 /* prepend http:// */
2124 new = g_malloc(sizeof("http://") + len);
2125 strcpy(new, "http://");
2126 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2127 proxy_uri = soup_uri_new(new);
2128 } else {
2129 proxy_uri = soup_uri_new(filename);
2131 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2136 void
2137 setup_signals() {
2138 #ifdef ENABLE_COOKIE_SUPPORT
2139 /* Headers. */
2140 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2141 #endif
2142 /* Accept-language header */
2143 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2144 /* window */
2145 g_object_connect(G_OBJECT(window),
2146 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2147 NULL);
2148 /* webview */
2149 g_object_connect(G_OBJECT(webview),
2150 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2151 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2152 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2153 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2154 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2155 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2156 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2157 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2158 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2159 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2160 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2161 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2162 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2163 NULL);
2164 /* webview adjustment */
2165 g_object_connect(G_OBJECT(adjust_v),
2166 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2167 NULL);
2168 /* inputbox */
2169 g_object_connect(G_OBJECT(inputbox),
2170 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2171 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2172 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2173 #ifdef ENABLE_INCREMENTAL_SEARCH
2174 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2175 #endif
2176 NULL);
2177 /* inspector */
2178 g_signal_connect(G_OBJECT(inspector),
2179 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2182 #ifdef ENABLE_COOKIE_SUPPORT
2183 void
2184 setup_cookies()
2186 if (file_cookie_jar)
2187 g_object_unref(file_cookie_jar);
2189 if (session_cookie_jar)
2190 g_object_unref(session_cookie_jar);
2192 session_cookie_jar = soup_cookie_jar_new();
2194 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2196 load_all_cookies();
2198 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2199 G_CALLBACK(update_cookie_jar), NULL);
2201 return;
2204 /* TA: XXX - we should be using this callback for any header-requests we
2205 * receive (hence the name "new_generic_request" -- but for now, its use
2206 * is limited to handling cookies.
2208 void
2209 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2210 SoupMessageHeaders *soup_msg_h;
2211 SoupURI *uri;
2212 const char *cookie_str;
2214 soup_msg_h = soup_msg->request_headers;
2215 soup_message_headers_remove(soup_msg_h, "Cookie");
2216 uri = soup_message_get_uri(soup_msg);
2217 if( (cookie_str = get_cookies(uri)) )
2218 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2220 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2222 return;
2225 const char *
2226 get_cookies(SoupURI *soup_uri) {
2227 const char *cookie_str;
2229 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2231 return cookie_str;
2234 void
2235 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2237 GSList *resp_cookie = NULL;
2238 SoupCookie *cookie;
2240 for(resp_cookie = soup_cookies_from_response(soup_msg);
2241 resp_cookie;
2242 resp_cookie = g_slist_next(resp_cookie))
2244 SoupDate *soup_date;
2245 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2247 if (cookie_timeout && cookie->expires == NULL) {
2248 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2249 soup_cookie_set_expires(cookie, soup_date);
2251 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2254 return;
2257 void
2258 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2260 if (!new) {
2261 /* Nothing to do. */
2262 return;
2265 SoupCookie *copy;
2266 copy = soup_cookie_copy(new);
2268 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2270 return;
2273 void
2274 load_all_cookies(void)
2276 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2278 /* Put them back in the session store. */
2279 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2281 for (; cookies_from_file;
2282 cookies_from_file = cookies_from_file->next)
2284 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2287 soup_cookies_free(cookies_from_file);
2289 return;
2292 #endif
2294 void
2295 mop_up(void) {
2296 /* Free up any nasty globals before exiting. */
2297 #ifdef ENABLE_COOKIE_SUPPORT
2298 if (cookie_store)
2299 g_free(cookie_store);
2300 #endif
2301 return;
2305 main(int argc, char *argv[]) {
2306 static Arg a;
2307 static char url[256] = "";
2308 static gboolean ver = false;
2309 static gboolean configfile_exists = FALSE;
2310 static const char *cfile = NULL;
2311 char *searchengines_file;
2312 static GOptionEntry opts[] = {
2313 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2314 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2315 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2316 { NULL }
2318 static GError *err;
2319 args = argv;
2321 /* command line argument: version */
2322 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2323 g_printerr("can't init gtk: %s\n", err->message);
2324 g_error_free(err);
2325 return EXIT_FAILURE;
2328 if (ver) {
2329 printf("%s\n", INTERNAL_VERSION);
2330 return EXIT_SUCCESS;
2333 if (cfile)
2334 configfile = g_strdup_printf(cfile);
2335 else
2336 configfile = g_strdup_printf(RCFILE);
2338 if (!g_thread_supported())
2339 g_thread_init(NULL);
2341 if (winid)
2342 embed = atoi(winid);
2344 setup_modkeys();
2345 make_keyslist();
2346 setup_gui();
2347 #ifdef ENABLE_COOKIE_SUPPORT
2348 setup_cookies();
2349 #endif
2351 /* Check if the specified file exists. */
2352 /* And only warn the user, if they explicitly asked for a config on the
2353 * command line.
2355 if (!(access(configfile, F_OK) == 0) && cfile) {
2356 char *feedback_str;
2358 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2359 give_feedback(feedback_str);
2360 } else if ((access(configfile, F_OK) == 0))
2361 configfile_exists = true;
2363 /* read config file */
2364 /* But only report errors if we failed, and the file existed. */
2365 if (!read_rcfile(configfile) && configfile_exists) {
2366 a.i = Error;
2367 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2368 echo(&a);
2369 g_free(a.s);
2370 g_free(configfile);
2373 make_searchengines_list(searchengines, LENGTH(searchengines));
2375 /* read searchengines. */
2376 searchengines_file = g_strdup_printf(SEARCHENGINES_STORAGE_FILENAME);
2377 switch (read_searchengines(searchengines_file)) {
2378 case SYNTAX_ERROR:
2379 a.i = Error;
2380 a.s = g_strdup_printf("Syntax error in searchengines file '%s'", searchengines_file);
2381 echo(&a);
2382 break;
2383 case READING_FAILED:
2384 a.i = Error;
2385 a.s = g_strdup_printf("Could not read searchengines file '%s'", searchengines_file);
2386 echo(&a);
2387 break;
2388 default:
2389 break;
2391 g_free(searchengines_file);
2393 /* command line argument: URL */
2394 if (argc > 1) {
2395 strncpy(url, argv[argc - 1], 255);
2396 } else {
2397 strncpy(url, startpage, 255);
2400 a.i = TargetCurrent;
2401 a.s = url;
2402 open_arg(&a);
2403 gtk_main();
2405 mop_up();
2407 return EXIT_SUCCESS;