Don't start download for failed requests.
[vimb.git] / src / main.c
1 /**
2  * vimb - a webkit based vim like browser.
3  *
4  * Copyright (C) 2012-2013 Daniel Carl
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://www.gnu.org/licenses/.
18  */
19
20 #include "config.h"
21 #include <sys/stat.h>
22 #include <math.h>
23 #include "main.h"
24 #include "util.h"
25 #include "command.h"
26 #include "setting.h"
27 #include "completion.h"
28 #include "dom.h"
29 #include "hints.h"
30 #include "shortcut.h"
31 #include "history.h"
32 #include "session.h"
33 #include "mode.h"
34 #include "normal.h"
35 #include "ex.h"
36 #include "input.h"
37 #include "map.h"
38 #include "default.h"
39 #include "pass.h"
40 #include "bookmark.h"
41
42 /* variables */
43 static char **args;
44 VbCore      vb;
45
46 /* callbacks */
47 static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec);
48 static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec);
49 static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec);
50 static void destroy_window_cb(GtkWidget *widget);
51 static void scroll_cb(GtkAdjustment *adjustment);
52 static WebKitWebView *inspector_new(WebKitWebInspector *inspector, WebKitWebView *webview);
53 static gboolean inspector_show(WebKitWebInspector *inspector);
54 static gboolean inspector_close(WebKitWebInspector *inspector);
55 static void inspector_finished(WebKitWebInspector *inspector);
56 static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event);
57 static gboolean new_window_policy_cb(
58     WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *request,
59     WebKitWebNavigationAction *navig, WebKitWebPolicyDecision *policy);
60 static WebKitWebView *create_web_view_cb(WebKitWebView *view, WebKitWebFrame *frame);
61 static void create_web_view_received_uri_cb(WebKitWebView *view);
62 static void hover_link_cb(WebKitWebView *webview, const char *title, const char *link);
63 static void title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, const char *title);
64 static gboolean mimetype_decision_cb(WebKitWebView *webview,
65     WebKitWebFrame *frame, WebKitNetworkRequest *request, char*
66     mime_type, WebKitWebPolicyDecision *decision);
67 static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec);
68
69 /* functions */
70 static void update_title(void);
71 static void run_user_script(WebKitWebFrame *frame);
72 static char *jsref_to_string(JSContextRef context, JSValueRef ref);
73 static void init_core(void);
74 static void read_config(void);
75 static void setup_signals();
76 static void init_files(void);
77 static gboolean hide_message();
78 static void set_status(const StatusType status);
79 static void input_print(gboolean force, const MessageType type, gboolean hide, const char *message);
80
81 void vb_echo_force(const MessageType type, gboolean hide, const char *error, ...)
82 {
83     char *buffer;
84     va_list args;
85
86     va_start(args, error);
87     buffer = g_strdup_vprintf(error, args);
88     va_end(args);
89
90     input_print(true, type, hide, buffer);
91     g_free(buffer);
92 }
93
94 void vb_echo(const MessageType type, gboolean hide, const char *error, ...)
95 {
96     char *buffer;
97     va_list args;
98
99     va_start(args, error);
100     buffer = g_strdup_vprintf(error, args);
101     va_end(args);
102
103     input_print(false, type, hide, buffer);
104     g_free(buffer);
105 }
106
107 static void input_print(gboolean force, const MessageType type, gboolean hide,
108     const char *message)
109 {
110     /* don't print message if the input is focussed */
111     if (!force && gtk_widget_is_focus(GTK_WIDGET(vb.gui.input))) {
112         return;
113     }
114
115     /* apply input style only if the message type was changed */
116     if (type != vb.state.input_type) {
117         vb.state.input_type = type;
118         vb_update_input_style();
119     }
120     vb_set_input_text(message);
121     if (hide) {
122         g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)hide_message, NULL);
123     }
124 }
125
126 /**
127  * Writes given text into the command line.
128  */
129 void vb_set_input_text(const char *text)
130 {
131     gtk_text_buffer_set_text(vb.gui.buffer, text, -1);
132 }
133
134 /**
135  * Retrieves the content of the command line.
136  * Retruned string must be freed with g_free.
137  */
138 char *vb_get_input_text(void)
139 {
140     GtkTextIter start, end;
141
142     gtk_text_buffer_get_bounds(vb.gui.buffer, &start, &end);
143     return gtk_text_buffer_get_text(vb.gui.buffer, &start, &end, false);
144 }
145
146 gboolean vb_eval_script(WebKitWebFrame *frame, char *script, char *file, char **value)
147 {
148     JSStringRef str, file_name;
149     JSValueRef exception = NULL, result = NULL;
150     JSContextRef js;
151
152     js        = webkit_web_frame_get_global_context(frame);
153     str       = JSStringCreateWithUTF8CString(script);
154     file_name = JSStringCreateWithUTF8CString(file);
155
156     result = JSEvaluateScript(js, str, JSContextGetGlobalObject(js), file_name, 0, &exception);
157     JSStringRelease(file_name);
158     JSStringRelease(str);
159
160     if (result) {
161         *value = jsref_to_string(js, result);
162         return true;
163     }
164
165     *value = jsref_to_string(js, exception);
166     return false;
167 }
168
169 gboolean vb_load_uri(const Arg *arg)
170 {
171     char *uri = NULL, *rp, *path = NULL;
172     struct stat st;
173
174     if (arg->s) {
175         path = g_strstrip(arg->s);
176     }
177     if (!path || !*path) {
178         path = vb.config.home_page;
179     }
180
181     if (strstr(path, "://") || !strncmp(path, "about:", 6)) {
182         uri = g_strdup(path);
183     } else if (stat(path, &st) == 0) {
184         /* check if the path is a file path */
185         rp  = realpath(path, NULL);
186         uri = g_strconcat("file://", rp, NULL);
187         free(rp);
188     } else if (strchr(path, ' ') || !strchr(path, '.')) {
189         /* use a shortcut if path contains spaces or no dot */
190         uri = shortcut_get_uri(path);
191     }
192
193     if (!uri) {
194         uri = g_strconcat("http://", path, NULL);
195     }
196
197     if (arg->i == VB_TARGET_NEW) {
198         guint i = 0;
199         char *cmd[7], xid[64];
200
201         cmd[i++] = *args;
202         if (vb.embed) {
203             cmd[i++] = "-e";
204             snprintf(xid, LENGTH(xid), "%u", (int)vb.embed);
205             cmd[i++] = xid;
206         }
207         if (vb.custom_config) {
208             cmd[i++] = "-c";
209             cmd[i++] = vb.custom_config;
210         }
211         cmd[i++] = uri;
212         cmd[i++] = NULL;
213
214         /* spawn a new browser instance */
215         g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
216     } else {
217         /* Load a web page into the browser instance */
218         webkit_web_view_load_uri(vb.gui.webview, uri);
219         /* show the url to be opened in the windo title until we receive the
220          * page title */
221         OVERWRITE_STRING(vb.state.title, uri);
222         update_title();
223     }
224     g_free(uri);
225
226     return true;
227 }
228
229 gboolean vb_set_clipboard(const Arg *arg)
230 {
231     gboolean result = false;
232     if (!arg->s) {
233         return result;
234     }
235
236     if (arg->i & VB_CLIPBOARD_PRIMARY) {
237         gtk_clipboard_set_text(PRIMARY_CLIPBOARD(), arg->s, -1);
238         result = true;
239     }
240     if (arg->i & VB_CLIPBOARD_SECONDARY) {
241         gtk_clipboard_set_text(SECONDARY_CLIPBOARD(), arg->s, -1);
242         result = true;
243     }
244
245     return result;
246 }
247
248 void vb_set_widget_font(GtkWidget *widget, const VbColor *fg, const VbColor *bg, PangoFontDescription *font)
249 {
250     VB_WIDGET_OVERRIDE_FONT(widget, font);
251     VB_WIDGET_OVERRIDE_TEXT(widget, VB_GTK_STATE_NORMAL, fg);
252     VB_WIDGET_OVERRIDE_COLOR(widget, VB_GTK_STATE_NORMAL, fg);
253     VB_WIDGET_OVERRIDE_BASE(widget, VB_GTK_STATE_NORMAL, bg);
254     VB_WIDGET_OVERRIDE_BACKGROUND(widget, VB_GTK_STATE_NORMAL, bg);
255 }
256
257 void vb_update_statusbar()
258 {
259     int max, val, num;
260     GString *status = g_string_new("");
261
262     /* show the active downloads */
263     if (vb.state.downloads) {
264         num = g_list_length(vb.state.downloads);
265         g_string_append_printf(status, " %d %s", num, num == 1 ? "download" : "downloads");
266     }
267
268     /* show load status of page or the downloads */
269     if (vb.state.progress != 100) {
270         g_string_append_printf(status, " [%i%%]", vb.state.progress);
271     }
272
273     /* show the scroll status */
274     max = gtk_adjustment_get_upper(vb.gui.adjust_v) - gtk_adjustment_get_page_size(vb.gui.adjust_v);
275     val = (int)floor(0.5 + (gtk_adjustment_get_value(vb.gui.adjust_v) / max * 100));
276
277     if (max == 0) {
278         g_string_append(status, " All");
279     } else if (val == 0) {
280         g_string_append(status, " Top");
281     } else if (val >= 100) {
282         g_string_append(status, " Bot");
283     } else {
284         g_string_append_printf(status, " %d%%", val);
285     }
286
287     gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.right), status->str);
288     g_string_free(status, true);
289 }
290
291 void vb_update_status_style(void)
292 {
293     StatusType type = vb.state.status_type;
294     vb_set_widget_font(
295         vb.gui.eventbox, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
296     );
297     vb_set_widget_font(
298         vb.gui.statusbar.left, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
299     );
300     vb_set_widget_font(
301         vb.gui.statusbar.right, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
302     );
303     vb_set_widget_font(
304         vb.gui.statusbar.cmd, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
305     );
306 }
307
308 void vb_update_input_style(void)
309 {
310     MessageType type = vb.state.input_type;
311     vb_set_widget_font(
312         vb.gui.input, &vb.style.input_fg[type], &vb.style.input_bg[type], vb.style.input_font[type]
313     );
314 }
315
316 void vb_update_urlbar(const char *uri)
317 {
318     Gui *gui = &vb.gui;
319 #ifdef FEATURE_HISTORY_INDICATOR
320     gboolean back, fwd;
321     char *str;
322
323     back = webkit_web_view_can_go_back(gui->webview);
324     fwd  = webkit_web_view_can_go_forward(gui->webview);
325
326     /* show history indicator only if there is something to show */
327     if (back || fwd) {
328         str = g_strdup_printf("%s [%s]", uri, back ? (fwd ? "-+" : "-") : "+");
329         gtk_label_set_text(GTK_LABEL(gui->statusbar.left), str);
330         g_free(str);
331     } else {
332         gtk_label_set_text(GTK_LABEL(gui->statusbar.left), uri);
333     }
334 #else
335     gtk_label_set_text(GTK_LABEL(gui->statusbar.left), uri);
336 #endif
337 }
338
339 void vb_quit(void)
340 {
341     const char *uri = GET_URI();
342     /* write last URL into file for recreation */
343     if (uri) {
344         g_file_set_contents(vb.files[FILES_CLOSED], uri, -1, NULL);
345     }
346
347     completion_clean();
348
349     webkit_web_view_stop_loading(vb.gui.webview);
350
351     map_cleanup();
352     mode_cleanup();
353     setting_cleanup();
354     shortcut_cleanup();
355     history_cleanup();
356
357     for (int i = 0; i < FILES_LAST; i++) {
358         g_free(vb.files[i]);
359     }
360
361     gtk_main_quit();
362 }
363
364 static gboolean hide_message()
365 {
366     input_print(false, VB_MSG_NORMAL, false, "");
367
368     return false;
369 }
370
371 static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec)
372 {
373     vb.state.progress = webkit_web_view_get_progress(view) * 100;
374     vb_update_statusbar();
375     update_title();
376 }
377
378 static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec)
379 {
380     if (vb.state.downloads) {
381         vb.state.progress = 0;
382         GList *ptr;
383         for (ptr = vb.state.downloads; ptr; ptr = g_list_next(ptr)) {
384             vb.state.progress += 100 * webkit_download_get_progress(ptr->data);
385         }
386         vb.state.progress /= g_list_length(vb.state.downloads);
387     }
388     vb_update_statusbar();
389     update_title();
390 }
391
392 static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec)
393 {
394     const char *uri = GET_URI();
395
396     switch (webkit_web_view_get_load_status(vb.gui.webview)) {
397         case WEBKIT_LOAD_PROVISIONAL:
398             /* update load progress in statusbar */
399             vb.state.progress = 0;
400             vb_update_statusbar();
401             update_title();
402             break;
403
404         case WEBKIT_LOAD_COMMITTED:
405             {
406                 WebKitWebFrame *frame = webkit_web_view_get_main_frame(vb.gui.webview);
407                 /* set the status */
408                 if (g_str_has_prefix(uri, "https://")) {
409                     WebKitWebDataSource *src      = webkit_web_frame_get_data_source(frame);
410                     WebKitNetworkRequest *request = webkit_web_data_source_get_request(src);
411                     SoupMessage *msg              = webkit_network_request_get_message(request);
412                     SoupMessageFlags flags        = soup_message_get_flags(msg);
413                     set_status(
414                         (flags & SOUP_MESSAGE_CERTIFICATE_TRUSTED) ? VB_STATUS_SSL_VALID : VB_STATUS_SSL_INVALID
415                     );
416                 } else {
417                     set_status(VB_STATUS_NORMAL);
418                 }
419
420                 /* inject the hinting javascript */
421                 hints_init(frame);
422
423                 /* run user script file */
424                 run_user_script(frame);
425             }
426
427             /* if we load a page from a submitted form, leafe the insert mode */
428             if (vb.mode->id == 'i') {
429                 mode_enter('n');
430             }
431
432             vb_update_statusbar();
433             vb_update_urlbar(uri);
434
435             break;
436
437         case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
438             break;
439
440         case WEBKIT_LOAD_FINISHED:
441             /* update load progress in statusbar */
442             vb.state.progress = 100;
443             vb_update_statusbar();
444             update_title();
445
446             if (strncmp(uri, "about:", 6)) {
447                 dom_check_auto_insert(view);
448                 history_add(HISTORY_URL, uri, webkit_web_view_get_title(view));
449             }
450             break;
451
452         case WEBKIT_LOAD_FAILED:
453             break;
454     }
455 }
456
457 static void destroy_window_cb(GtkWidget *widget)
458 {
459     vb_quit();
460 }
461
462 static void scroll_cb(GtkAdjustment *adjustment)
463 {
464     vb_update_statusbar();
465 }
466
467 static WebKitWebView *inspector_new(WebKitWebInspector *inspector, WebKitWebView *webview)
468 {
469     return WEBKIT_WEB_VIEW(webkit_web_view_new());
470 }
471
472 static gboolean inspector_show(WebKitWebInspector *inspector)
473 {
474     WebKitWebView *webview;
475     int height;
476
477     if (vb.state.is_inspecting) {
478         return false;
479     }
480
481     webview = webkit_web_inspector_get_web_view(inspector);
482
483     /* use about 1/3 of window height for the inspector */
484     gtk_window_get_size(GTK_WINDOW(vb.gui.window), NULL, &height);
485     gtk_paned_set_position(GTK_PANED(vb.gui.pane), 2 * height / 3);
486
487     gtk_paned_pack2(GTK_PANED(vb.gui.pane), GTK_WIDGET(webview), true, true);
488     gtk_widget_show(GTK_WIDGET(webview));
489
490     vb.state.is_inspecting = true;
491
492     return true;
493 }
494
495 static gboolean inspector_close(WebKitWebInspector *inspector)
496 {
497     WebKitWebView *webview;
498
499     if (!vb.state.is_inspecting) {
500         return false;
501     }
502     webview = webkit_web_inspector_get_web_view(inspector);
503     gtk_widget_hide(GTK_WIDGET(webview));
504     gtk_widget_destroy(GTK_WIDGET(webview));
505
506     vb.state.is_inspecting = false;
507
508     return true;
509 }
510
511 static void inspector_finished(WebKitWebInspector *inspector)
512 {
513     g_free(vb.gui.inspector);
514 }
515
516 static void set_status(const StatusType status)
517 {
518     if (vb.state.status_type != status) {
519         vb.state.status_type = status;
520         /* update the statusbar style only if the status changed */
521         vb_update_status_style();
522     }
523 }
524
525 static void run_user_script(WebKitWebFrame *frame)
526 {
527     char *js = NULL, *value = NULL;
528     GError *error = NULL;
529
530     if (g_file_test(vb.files[FILES_SCRIPT], G_FILE_TEST_IS_REGULAR)
531         && g_file_get_contents(vb.files[FILES_SCRIPT], &js, NULL, &error)
532     ) {
533         gboolean success = vb_eval_script(frame, js, vb.files[FILES_SCRIPT], &value);
534         if (!success) {
535             fprintf(stderr, "%s", value);
536         }
537         g_free(value);
538         g_free(js);
539     }
540 }
541
542 static char *jsref_to_string(JSContextRef context, JSValueRef ref)
543 {
544     char *string;
545     JSStringRef str_ref = JSValueToStringCopy(context, ref, NULL);
546     size_t len          = JSStringGetMaximumUTF8CStringSize(str_ref);
547
548     string = g_new0(char, len);
549     JSStringGetUTF8CString(str_ref, string, len);
550     JSStringRelease(str_ref);
551
552     return string;
553 }
554
555 static void init_core(void)
556 {
557     Gui *gui = &vb.gui;
558
559     if (vb.embed) {
560         gui->window = gtk_plug_new(vb.embed);
561     } else {
562         gui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
563 #ifdef HAS_GTK3
564         gtk_window_set_has_resize_grip(GTK_WINDOW(gui->window), false);
565 #endif
566         gtk_window_set_wmclass(GTK_WINDOW(gui->window), PROJECT, PROJECT_UCFIRST);
567         gtk_window_set_role(GTK_WINDOW(gui->window), PROJECT_UCFIRST);
568     }
569
570     GdkGeometry hints = {10, 10};
571     gtk_window_set_default_size(GTK_WINDOW(gui->window), WIN_WIDTH, WIN_HEIGHT);
572     gtk_window_set_title(GTK_WINDOW(gui->window), PROJECT "/" VERSION);
573     gtk_window_set_geometry_hints(GTK_WINDOW(gui->window), NULL, &hints, GDK_HINT_MIN_SIZE);
574     gtk_window_set_icon(GTK_WINDOW(gui->window), NULL);
575     gtk_widget_set_name(GTK_WIDGET(gui->window), PROJECT);
576
577     /* Create a browser instance */
578     gui->webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
579     gui->inspector = webkit_web_view_get_inspector(gui->webview);
580
581     /* Create a scrollable area */
582     gui->scroll = gtk_scrolled_window_new(NULL, NULL);
583     gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->scroll));
584     gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->scroll));
585
586 #ifdef FEATURE_NO_SCROLLBARS
587 #ifdef HAS_GTK3
588     /* set the default style for the application - this can be overwritten by
589      * the users style in gtk-3.0/gtk.css */
590     char *style = "GtkScrollbar{-GtkRange-slider-width:0;-GtkRange-trough-border:0;}";
591     GtkCssProvider *provider = gtk_css_provider_get_default();
592     gtk_css_provider_load_from_data(provider, style, -1, NULL);
593     gtk_style_context_add_provider_for_screen(
594         gdk_screen_get_default(),
595         GTK_STYLE_PROVIDER(provider),
596         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
597     );
598 #else /* no GTK3 */
599     /* GTK_POLICY_NEVER with gtk3 disallows window resizing and scrolling */
600     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
601 #endif
602 #endif
603
604     /* Prepare the command line */
605     gui->input  = gtk_text_view_new();
606     gui->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gui->input));
607
608 #ifdef HAS_GTK3
609     gui->pane            = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
610     gui->box             = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
611     gui->statusbar.box   = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
612 #else
613     gui->pane            = gtk_vpaned_new();
614     gui->box             = GTK_BOX(gtk_vbox_new(false, 0));
615     gui->statusbar.box   = GTK_BOX(gtk_hbox_new(false, 0));
616 #endif
617     gui->statusbar.left  = gtk_label_new(NULL);
618     gui->statusbar.right = gtk_label_new(NULL);
619     gui->statusbar.cmd   = gtk_label_new(NULL);
620
621     /* Prepare the event box */
622     gui->eventbox = gtk_event_box_new();
623
624     gtk_paned_pack1(GTK_PANED(gui->pane), GTK_WIDGET(gui->box), true, true);
625
626     /* Put all part together */
627     gtk_container_add(GTK_CONTAINER(gui->scroll), GTK_WIDGET(gui->webview));
628     gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar.box));
629     gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane));
630     gtk_misc_set_alignment(GTK_MISC(gui->statusbar.left), 0.0, 0.0);
631     gtk_misc_set_alignment(GTK_MISC(gui->statusbar.right), 1.0, 0.0);
632     gtk_misc_set_alignment(GTK_MISC(gui->statusbar.cmd), 1.0, 0.0);
633     gtk_box_pack_start(gui->statusbar.box, gui->statusbar.left, true, true, 2);
634     gtk_box_pack_start(gui->statusbar.box, gui->statusbar.cmd, false, false, 0);
635     gtk_box_pack_start(gui->statusbar.box, gui->statusbar.right, false, false, 2);
636
637     gtk_box_pack_start(gui->box, gui->scroll, true, true, 0);
638     gtk_box_pack_start(gui->box, gui->eventbox, false, false, 0);
639     gtk_box_pack_end(gui->box, gui->input, false, false, 0);
640
641     /* initialize the modes */
642     mode_init();
643     mode_add('n', normal_enter, normal_leave, normal_keypress, NULL);
644     mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed);
645     mode_add('i', input_enter, input_leave, input_keypress, NULL);
646     mode_add('p', pass_enter, pass_leave, pass_keypress, NULL);
647
648     init_files();
649     session_init();
650     setting_init();
651     shortcut_init();
652     read_config();
653
654     /* initially apply input style */
655     vb_update_input_style();
656
657     setup_signals();
658
659     /* enter normal mode */
660     mode_enter('n');
661
662     /* make sure the main window and all its contents are visible */
663     gtk_widget_show_all(gui->window);
664 }
665
666 static void read_config(void)
667 {
668     char *line, **lines;
669
670     /* load default config */
671     for (guint i = 0; default_config[i] != NULL; i++) {
672         if (!ex_run_string(default_config[i])) {
673             fprintf(stderr, "Invalid default config: %s\n", default_config[i]);
674         }
675     }
676
677     /* read config from config files */
678     lines = util_get_lines(vb.files[FILES_CONFIG]);
679
680     if (lines) {
681         int length = g_strv_length(lines) - 1;
682         for (int i = 0; i < length; i++) {
683             line = lines[i];
684             if (*line == '#') {
685                 continue;
686             }
687             if (!ex_run_string(line)) {
688                 fprintf(stderr, "Invalid config: %s\n", line);
689             }
690         }
691     }
692     g_strfreev(lines);
693 }
694
695 static void setup_signals()
696 {
697     /* Set up callbacks so that if either the main window or the browser
698      * instance is closed, the program will exit */
699     g_signal_connect(vb.gui.window, "destroy", G_CALLBACK(destroy_window_cb), NULL);
700     g_object_connect(
701         G_OBJECT(vb.gui.webview),
702         "signal::notify::progress", G_CALLBACK(webview_progress_cb), NULL,
703         "signal::notify::load-status", G_CALLBACK(webview_load_status_cb), NULL,
704         "signal::button-release-event", G_CALLBACK(button_relase_cb), NULL,
705         "signal::new-window-policy-decision-requested", G_CALLBACK(new_window_policy_cb), NULL,
706         "signal::create-web-view", G_CALLBACK(create_web_view_cb), NULL,
707         "signal::hovering-over-link", G_CALLBACK(hover_link_cb), NULL,
708         "signal::title-changed", G_CALLBACK(title_changed_cb), NULL,
709         "signal::mime-type-policy-decision-requested", G_CALLBACK(mimetype_decision_cb), NULL,
710         "signal::download-requested", G_CALLBACK(vb_download), NULL,
711         "signal::should-show-delete-interface-for-element", G_CALLBACK(gtk_false), NULL,
712         NULL
713     );
714
715 #ifdef FEATURE_NO_SCROLLBARS
716     WebKitWebFrame *frame = webkit_web_view_get_main_frame(vb.gui.webview);
717     g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed", G_CALLBACK(gtk_true), NULL);
718 #endif
719
720     g_signal_connect(
721         G_OBJECT(vb.gui.window), "key-press-event", G_CALLBACK(map_keypress), NULL
722     );
723     g_signal_connect(
724         G_OBJECT(vb.gui.input), "focus-in-event", G_CALLBACK(mode_input_focusin), NULL
725     );
726     g_object_connect(
727         G_OBJECT(vb.gui.buffer),
728         "signal::changed", G_CALLBACK(mode_input_changed), NULL,
729         NULL
730     );
731
732     /* webview adjustment */
733     g_object_connect(G_OBJECT(vb.gui.adjust_v),
734         "signal::value-changed", G_CALLBACK(scroll_cb), NULL,
735         NULL
736     );
737
738     /* inspector */
739     g_object_connect(
740         G_OBJECT(vb.gui.inspector),
741         "signal::inspect-web-view", G_CALLBACK(inspector_new), NULL,
742         "signal::show-window", G_CALLBACK(inspector_show), NULL,
743         "signal::close-window", G_CALLBACK(inspector_close), NULL,
744         "signal::finished", G_CALLBACK(inspector_finished), NULL,
745         NULL
746     );
747 }
748
749 static void init_files(void)
750 {
751     char *path = util_get_config_dir();
752
753     if (vb.custom_config) {
754         char *rp = realpath(vb.custom_config, NULL);
755         vb.files[FILES_CONFIG] = g_strdup(rp);
756         free(rp);
757     } else {
758         vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL);
759         util_create_file_if_not_exists(vb.files[FILES_CONFIG]);
760     }
761
762 #ifdef FEATURE_COOKIE
763     vb.files[FILES_COOKIE] = g_build_filename(path, "cookies", NULL);
764     util_create_file_if_not_exists(vb.files[FILES_COOKIE]);
765 #endif
766
767     vb.files[FILES_CLOSED] = g_build_filename(path, "closed", NULL);
768     util_create_file_if_not_exists(vb.files[FILES_CLOSED]);
769
770     vb.files[FILES_HISTORY] = g_build_filename(path, "history", NULL);
771     util_create_file_if_not_exists(vb.files[FILES_HISTORY]);
772
773     vb.files[FILES_COMMAND] = g_build_filename(path, "command", NULL);
774     util_create_file_if_not_exists(vb.files[FILES_COMMAND]);
775
776     vb.files[FILES_SEARCH] = g_build_filename(path, "search", NULL);
777     util_create_file_if_not_exists(vb.files[FILES_SEARCH]);
778
779     vb.files[FILES_BOOKMARK] = g_build_filename(path, "bookmark", NULL);
780     util_create_file_if_not_exists(vb.files[FILES_BOOKMARK]);
781
782 #ifdef FEATURE_QUEUE
783     vb.files[FILES_QUEUE] = g_build_filename(path, "queue", NULL);
784     util_create_file_if_not_exists(vb.files[FILES_QUEUE]);
785 #endif
786
787     vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL);
788
789     vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL);
790
791     g_free(path);
792 }
793
794 static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event)
795 {
796     gboolean propagate = false;
797     WebKitHitTestResultContext context;
798
799     WebKitHitTestResult *result = webkit_web_view_get_hit_test_result(webview, event);
800
801     g_object_get(result, "context", &context, NULL);
802     if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
803         mode_enter('i');
804         propagate = true;
805     } else if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK && event->button == 2) {
806         /* middle mouse click onto link */
807         Arg a = {VB_TARGET_NEW};
808         g_object_get(result, "link-uri", &a.s, NULL);
809         vb_load_uri(&a);
810
811         propagate = true;
812     }
813     g_object_unref(result);
814
815     return propagate;
816 }
817
818 static gboolean new_window_policy_cb(
819     WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *request,
820     WebKitWebNavigationAction *navig, WebKitWebPolicyDecision *policy)
821 {
822     if (webkit_web_navigation_action_get_reason(navig) == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
823         webkit_web_policy_decision_ignore(policy);
824         /* open in a new window */
825         Arg a = {VB_TARGET_NEW, (char*)webkit_network_request_get_uri(request)};
826         vb_load_uri(&a);
827         return true;
828     }
829     return false;
830 }
831
832 static WebKitWebView *create_web_view_cb(WebKitWebView *view, WebKitWebFrame *frame)
833 {
834     WebKitWebView *new = WEBKIT_WEB_VIEW(webkit_web_view_new());
835
836     /* wait until the new webview receives its new URI */
837     g_signal_connect(new, "notify::uri", G_CALLBACK(create_web_view_received_uri_cb), NULL);
838
839     return new;
840 }
841
842 static void create_web_view_received_uri_cb(WebKitWebView *view)
843 {
844     Arg a = {VB_TARGET_NEW, (char*)webkit_web_view_get_uri(view)};
845     /* destroy temporary webview */
846     webkit_web_view_stop_loading(view);
847     gtk_widget_destroy(GTK_WIDGET(view));
848     vb_load_uri(&a);
849 }
850
851 static void hover_link_cb(WebKitWebView *webview, const char *title, const char *link)
852 {
853     char *message;
854     if (link) {
855         message = g_strconcat("Link: ", link, NULL);
856         gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.left), message);
857         g_free(message);
858     } else {
859         vb_update_urlbar(webkit_web_view_get_uri(webview));
860     }
861 }
862
863 static void title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, const char *title)
864 {
865     OVERWRITE_STRING(vb.state.title, title);
866     update_title();
867 }
868
869 static void update_title(void)
870 {
871 #ifdef FEATURE_TITLE_PROGRESS
872     /* show load status of page or the downloads */
873     if (vb.state.progress != 100) {
874         char *title = g_strdup_printf(
875             "[%i%%] %s",
876             vb.state.progress,
877             vb.state.title ? vb.state.title : ""
878         );
879         gtk_window_set_title(GTK_WINDOW(vb.gui.window), title);
880         g_free(title);
881         return;
882     }
883 #endif
884     if (vb.state.title) {
885         gtk_window_set_title(GTK_WINDOW(vb.gui.window), vb.state.title);
886     }
887 }
888
889 static gboolean mimetype_decision_cb(WebKitWebView *webview,
890     WebKitWebFrame *frame, WebKitNetworkRequest *request, char *mime_type,
891     WebKitWebPolicyDecision *decision)
892 {
893     /* don't start download if request failed or stopped by proxy */
894     if (!mime_type || *mime_type == '\0') {
895         return false;
896     }
897     if (webkit_web_view_can_show_mime_type(webview, mime_type) == false) {
898         webkit_web_policy_decision_download(decision);
899
900         return true;
901     }
902     return false;
903 }
904
905 gboolean vb_download(WebKitWebView *view, WebKitDownload *download, const char *path)
906 {
907     WebKitDownloadStatus status;
908     char *uri, *file;
909
910     /* prepare the path to save the donwload */
911     if (path) {
912         file = util_build_path(path, vb.config.download_dir);
913     } else {
914         path = webkit_download_get_suggested_filename(download);
915         if (!path) {
916             path = PROJECT "-donwload";
917         }
918         file = util_build_path(path, vb.config.download_dir);
919     }
920
921     /* build the file uri from file path */
922     uri = g_filename_to_uri(file, NULL, NULL);
923     webkit_download_set_destination_uri(download, uri);
924     g_free(file);
925     g_free(uri);
926
927     guint64 size = webkit_download_get_total_size(download);
928     if (size > 0) {
929         vb_echo(VB_MSG_NORMAL, false, "Download %s [~%uB] started ...", path, size);
930     } else {
931         vb_echo(VB_MSG_NORMAL, false, "Download %s started ...", path);
932     }
933
934     status = webkit_download_get_status(download);
935     if (status == WEBKIT_DOWNLOAD_STATUS_CREATED) {
936         webkit_download_start(download);
937     }
938
939     /* prepend the download to the download list */
940     vb.state.downloads = g_list_prepend(vb.state.downloads, download);
941
942     /* connect signal handler to check if the download is done */
943     g_signal_connect(download, "notify::status", G_CALLBACK(download_progress_cp), NULL);
944     g_signal_connect(download, "notify::progress", G_CALLBACK(webview_download_progress_cb), NULL);
945
946     vb_update_statusbar();
947
948     return true;
949 }
950
951 static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec)
952 {
953     WebKitDownloadStatus status = webkit_download_get_status(download);
954
955     if (status == WEBKIT_DOWNLOAD_STATUS_STARTED || status == WEBKIT_DOWNLOAD_STATUS_CREATED) {
956         return;
957     }
958
959     const char *file = webkit_download_get_destination_uri(download);
960     /* skip the file protocoll for the display */
961     if (!strncmp(file, "file://", 7)) {
962         file += 7;
963     }
964     if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
965         vb_echo(VB_MSG_ERROR, false, "Error downloading %s", file);
966     } else {
967         vb_echo(VB_MSG_NORMAL, false, "Download %s finished", file);
968     }
969
970     /* remove the donwload from the list */
971     vb.state.downloads = g_list_remove(vb.state.downloads, download);
972
973     vb_update_statusbar();
974 }
975
976 int main(int argc, char *argv[])
977 {
978     static char *winid = NULL;
979     static gboolean ver = false;
980     static GError *err;
981
982     vb.custom_config = NULL;
983     static GOptionEntry opts[] = {
984         {"version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Print version", NULL},
985         {"config", 'c', 0, G_OPTION_ARG_STRING, &vb.custom_config, "Custom cufiguration file", NULL},
986         {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL},
987         {NULL}
988     };
989     /* Initialize GTK+ */
990     if (!gtk_init_with_args(&argc, &argv, "[URI]", opts, NULL, &err)) {
991         g_printerr("can't init gtk: %s\n", err->message);
992         g_error_free(err);
993
994         return EXIT_FAILURE;
995     }
996
997     if (ver) {
998         fprintf(stdout, "%s/%s\n", PROJECT, VERSION);
999         return EXIT_SUCCESS;
1000     }
1001
1002     /* save arguments */
1003     args = argv;
1004
1005     if (winid) {
1006         vb.embed = strtol(winid, NULL, 0);
1007     }
1008
1009     init_core();
1010
1011     /* command line argument: URL */
1012     vb_load_uri(&(Arg){VB_TARGET_CURRENT, argc > 1 ? argv[argc - 1] : NULL});
1013
1014     /* Run the main GTK+ event loop */
1015     gtk_main();
1016
1017     return EXIT_SUCCESS;
1018 }