1 /**
2 * vimb - a webkit based vim like browser.
3 *
4 * Copyright (C) 2012-2018 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 <gdk/gdkx.h>
21 #include <gtk/gtk.h>
22 #include <gtk/gtkx.h>
23 #include <libsoup/soup.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <webkit2/webkit2.h>
30
31 #include "../version.h"
32 #include "ascii.h"
33 #include "command.h"
34 #include "completion.h"
35 #include "config.h"
36 #include "ex.h"
37 #include "ext-proxy.h"
38 #include "handler.h"
39 #include "history.h"
40 #include "input.h"
41 #include "js.h"
42 #include "main.h"
43 #include "map.h"
44 #include "normal.h"
45 #include "setting.h"
46 #include "shortcut.h"
47 #include "util.h"
48 #include "autocmd.h"
49 #include "file-storage.h"
50
51 static void client_destroy(Client *c);
52 static Client *client_new(WebKitWebView *webview);
53 static void client_show(WebKitWebView *webview, Client *c);
54 static GtkWidget *create_window(Client *c);
55 static gboolean input_clear(Client *c);
56 static void input_print(Client *c, MessageType type, gboolean hide,
57 const char *message);
58 static gboolean is_plausible_uri(const char *path);
59 static void marks_clear(Client *c);
60 static void mode_free(Mode *mode);
61 static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data);
62 static void on_webctx_download_started(WebKitWebContext *webctx,
63 WebKitDownload *download, Client *c);
64 static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data);
65 static gboolean on_webdownload_decide_destination(WebKitDownload *download,
66 gchar *suggested_filename, Client *c);
67 static void on_webdownload_response_received(WebKitDownload *download,
68 GParamSpec *ps, Client *c);
69 static void spawn_download_command(Client *c, WebKitURIResponse *response);
70 static void on_webdownload_failed(WebKitDownload *download,
71 GError *error, Client *c);
72 static void on_webdownload_finished(WebKitDownload *download, Client *c);
73 static void on_webdownload_received_data(WebKitDownload *download,
74 guint64 data_length, Client *c);
75 static void on_webview_close(WebKitWebView *webview, Client *c);
76 static WebKitWebView *on_webview_create(WebKitWebView *webview,
77 WebKitNavigationAction *navact, Client *c);
78 static gboolean on_webview_decide_policy(WebKitWebView *webview,
79 WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c);
80 static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec);
81 static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec);
82 static void decide_response(Client *c, WebKitPolicyDecision *dec);
83 static void on_webview_load_changed(WebKitWebView *webview,
84 WebKitLoadEvent event, Client *c);
85 static void on_webview_mouse_target_changed(WebKitWebView *webview,
86 WebKitHitTestResult *result, guint modifiers, Client *c);
87 static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
88 GParamSpec *spec, Client *c);
89 static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec,
90 Client *c);
91 static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec,
92 Client *c);
93 static void on_webview_ready_to_show(WebKitWebView *webview, Client *c);
94 static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c);
95 static gboolean on_webview_authenticate(WebKitWebView *webview,
96 WebKitAuthenticationRequest *request, Client *c);
97 static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c);
98 static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c);
99 static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c);
100 static void on_window_destroy(GtkWidget *window, Client *c);
101 static gboolean quit(Client *c);
102 static void read_from_stdin(Client *c);
103 static void register_cleanup(Client *c);
104 static void update_title(Client *c);
105 static void update_urlbar(Client *c);
106 static void set_statusbar_style(Client *c, StatusType type);
107 static void set_title(Client *c, const char *title);
108 static void spawn_new_instance(const char *uri);
109 #ifdef FREE_ON_QUIT
110 static void vimb_cleanup(void);
111 #endif
112 static void vimb_setup(void);
113 static WebKitWebView *webview_new(Client *c, WebKitWebView *webview);
114 static void on_counted_matches(WebKitFindController *finder, guint count, Client *c);
115 static gboolean on_permission_request(WebKitWebView *webview,
116 WebKitPermissionRequest *request, Client *c);
117 static void on_script_message_focus(WebKitUserContentManager *manager,
118 WebKitJavascriptResult *res, gpointer data);
119 static gboolean profileOptionArgFunc(const gchar *option_name,
120 const gchar *value, gpointer data, GError **error);
121 static gboolean autocmdOptionArgFunc(const gchar *option_name,
122 const gchar *value, gpointer data, GError **error);
123
124 struct Vimb vb;
125
126 /**
127 * Set the destination for a download according to suggested file name and
128 * possible given path.
129 */
vb_download_set_destination(Client * c,WebKitDownload * download,char * suggested_filename,const char * path)130 gboolean vb_download_set_destination(Client *c, WebKitDownload *download,
131 char *suggested_filename, const char *path)
132 {
133 char *download_path, *dir, *file, *uri, *basename = NULL,
134 *decoded_uri = NULL;
135 const char *download_uri;
136 download_path = GET_CHAR(c, "download-path");
137
138 if (!suggested_filename || !*suggested_filename) {
139 /* Try to find a matching name if there is no suggested filename. */
140 download_uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
141 decoded_uri = soup_uri_decode(download_uri);
142 basename = g_filename_display_basename(decoded_uri);
143 g_free(decoded_uri);
144
145 suggested_filename = basename;
146 }
147
148 /* Prepare the path to save the download. */
149 if (path && *path) {
150 file = util_build_path(c->state, path, download_path);
151
152 /* if file is an directory append a file name */
153 if (g_file_test(file, (G_FILE_TEST_IS_DIR))) {
154 dir = file;
155 file = g_build_filename(dir, suggested_filename, NULL);
156 g_free(dir);
157 }
158 } else {
159 file = util_build_path(c->state, suggested_filename, download_path);
160 }
161
162 g_free(basename);
163
164 if (!file) {
165 return FALSE;
166 }
167
168 /* If the filepath exists already insert numerical suffix before file
169 * extension. */
170 if (g_file_test(file, G_FILE_TEST_EXISTS)) {
171 const char *dot_pos;
172 char *num = NULL;
173 GString *tmp;
174 gssize suffix;
175 int i = 1;
176
177 /* position on .tar. (special case, extension with two dots),
178 * position on last dot (if any) otherwise */
179 if (!(dot_pos = strstr(file, ".tar."))) {
180 dot_pos = strrchr(file, '.');
181 }
182
183 /* the position to insert the suffix at */
184 if (dot_pos) {
185 suffix = dot_pos - file;
186 } else {
187 suffix = strlen(file);
188 }
189
190 tmp = g_string_new(NULL);
191
192 /* Construct a new complete download filepath with suffix before the
193 * file extension. */
194 do {
195 num = g_strdup_printf("_%d", i++);
196 g_string_assign(tmp, file);
197 g_string_insert(tmp, suffix, num);
198 g_free(num);
199 } while (g_file_test(tmp->str, G_FILE_TEST_EXISTS));
200
201 file = g_strdup(tmp->str);
202 g_string_free(tmp, TRUE);
203 }
204
205 /* Build URI from filepath. */
206 uri = g_filename_to_uri(file, NULL, NULL);
207 g_free(file);
208
209 /* configure download */
210 g_assert(uri);
211 webkit_download_set_allow_overwrite(download, FALSE);
212 webkit_download_set_destination(download, uri);
213 g_free(uri);
214
215 return TRUE;
216 }
217
218 /**
219 * Write text to the inpubox if this isn't focused.
220 */
vb_echo(Client * c,MessageType type,gboolean hide,const char * error,...)221 void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...)
222 {
223 char *buffer;
224 va_list args;
225 /* Don't write to input box in case this is focused, might be the user is
226 * typing in it. */
227 if (gtk_widget_is_focus(GTK_WIDGET(c->input))) {
228 return;
229 }
230
231 va_start(args, error);
232 buffer = g_strdup_vprintf(error, args);
233 va_end(args);
234
235 input_print(c, type, hide, buffer);
236 g_free(buffer);
237 }
238
239 /**
240 * Write text to the inpubox independent if this is focused or not.
241 * Note that this could disturb the user during typing into inputbox.
242 */
vb_echo_force(Client * c,MessageType type,gboolean hide,const char * error,...)243 void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...)
244 {
245 char *buffer;
246 va_list args;
247
248 va_start(args, error);
249 buffer = g_strdup_vprintf(error, args);
250 va_end(args);
251
252 input_print(c, type, hide, buffer);
253 g_free(buffer);
254 }
255
256 /**
257 * Enter into the new given mode and leave possible active current mode.
258 */
vb_enter(Client * c,char id)259 void vb_enter(Client *c, char id)
260 {
261 Mode *new = g_hash_table_lookup(vb.modes, GINT_TO_POINTER(id));
262
263 g_return_if_fail(new != NULL);
264
265 if (c->mode) {
266 /* don't do anything if the mode isn't a new one */
267 if (c->mode == new) {
268 return;
269 }
270
271 /* if there is a active mode, leave this first */
272 if (c->mode->leave) {
273 c->mode->leave(c);
274 }
275 }
276
277 /* reset the flags of the new entered mode */
278 new->flags = 0;
279
280 /* set the new mode so that it is available also in enter function */
281 c->mode = new;
282 /* call enter only if the new mode isn't the current mode */
283 if (new->enter) {
284 new->enter(c);
285 }
286
287 #ifndef TESTLIB
288 vb_statusbar_update(c);
289 #endif
290 }
291
292 /**
293 * Set the prompt chars and switch to new mode.
294 *
295 * @id: Mode id.
296 * @prompt: Prompt string to set as current prompt.
297 * @print_prompt: Indicates if the new set prompt should be put into inputbox
298 * after switching the mode.
299 */
vb_enter_prompt(Client * c,char id,const char * prompt,gboolean print_prompt)300 void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt)
301 {
302 /* set the prompt to be accessible in vb_enter */
303 strncpy(c->state.prompt, prompt, PROMPT_SIZE - 1);
304 c->state.prompt[PROMPT_SIZE - 1] = '\0';
305
306 vb_enter(c, id);
307
308 if (print_prompt) {
309 /* set it after the mode was entered so that the modes input change
310 * event listener could grep the new prompt */
311 vb_echo_force(c, MSG_NORMAL, FALSE, c->state.prompt);
312 }
313 }
314
315 /**
316 * Returns the client for given page id.
317 */
vb_get_client_for_page_id(guint64 pageid)318 Client *vb_get_client_for_page_id(guint64 pageid)
319 {
320 Client *c;
321 /* Search for the client with the same page id. */
322 for (c = vb.clients; c && c->page_id != pageid; c = c->next);
323
324 if (c) {
325 return c;
326 }
327 return NULL;
328 }
329
330 /**
331 * Retrieves the content of the command line.
332 * Returned string must be freed with g_free.
333 */
vb_input_get_text(Client * c)334 char *vb_input_get_text(Client *c)
335 {
336 GtkTextIter start, end;
337
338 gtk_text_buffer_get_bounds(c->buffer, &start, &end);
339 return gtk_text_buffer_get_text(c->buffer, &start, &end, FALSE);
340 }
341
342 /**
343 * Writes given text into the command line.
344 */
vb_input_set_text(Client * c,const char * text)345 void vb_input_set_text(Client *c, const char *text)
346 {
347 gtk_text_buffer_set_text(c->buffer, text, -1);
348 if (c->config.input_autohide) {
349 gtk_widget_set_visible(GTK_WIDGET(c->input), *text != '\0');
350 }
351 }
352
353 /**
354 * Set the style of the inputbox according to current input type (normal or
355 * error).
356 */
vb_input_update_style(Client * c)357 void vb_input_update_style(Client *c)
358 {
359 MessageType type = c->state.input_type;
360
361 if (type == MSG_ERROR) {
362 gtk_style_context_add_class(gtk_widget_get_style_context(c->input), "error");
363 } else {
364 gtk_style_context_remove_class(gtk_widget_get_style_context(c->input), "error");
365 }
366 }
367
368 /**
369 * Load the a uri given in Arg. This function handles also shortcuts and local
370 * file paths.
371 *
372 * If arg.i = TARGET_CURRENT, the url is opened into the current webview.
373 * TARGET_RELATED causes the generation of a new window within the current
374 * instance of vimb with a own, but related webview. And TARGET_NEW spawns a
375 * new instance of vimb with the given uri.
376 */
vb_load_uri(Client * c,const Arg * arg)377 gboolean vb_load_uri(Client *c, const Arg *arg)
378 {
379 char *uri = NULL, *rp, *path = NULL;
380 struct stat st;
381
382 if (arg->s) {
383 path = g_strstrip(arg->s);
384 }
385 if (!path || !*path) {
386 path = GET_CHAR(c, "home-page");
387 }
388
389 /* If path contains :// but no space we open it direct. This is required
390 * to use :// also with shortcuts */
391 if ((strstr(path, "://") && !strchr(path, ' ')) || !strncmp(path, "about:", 6)) {
392 uri = g_strdup(path);
393 } else if (stat(path, &st) == 0) {
394 /* check if the path is a file path */
395 rp = realpath(path, NULL);
396 uri = g_strconcat("file://", rp, NULL);
397 free(rp);
398 } else if (!is_plausible_uri(path)) {
399 /* use a shortcut if path contains spaces or doesn't contain typical
400 * tokens ('.', [:] for IPv6 addresses, 'localhost') */
401 uri = shortcut_get_uri(c->config.shortcuts, path);
402 }
403
404 if (!uri) {
405 uri = g_strconcat("http://", path, NULL);
406 }
407
408 if (arg->i == TARGET_CURRENT) {
409 /* Load the uri into the browser instance. */
410 webkit_web_view_load_uri(c->webview, uri);
411 set_title(c, uri);
412 } else if (arg->i == TARGET_NEW) {
413 spawn_new_instance(uri);
414 } else { /* TARGET_RELATED */
415 Client *newclient = client_new(c->webview);
416 /* Load the uri into the new client. */
417 webkit_web_view_load_uri(newclient->webview, uri);
418 set_title(c, uri);
419 }
420 g_free(uri);
421
422 return TRUE;
423 }
424
425 /**
426 * Creates and add a new mode with given callback functions.
427 */
vb_mode_add(char id,ModeTransitionFunc enter,ModeTransitionFunc leave,ModeKeyFunc keypress,ModeInputChangedFunc input_changed)428 void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
429 ModeKeyFunc keypress, ModeInputChangedFunc input_changed)
430 {
431 Mode *new = g_slice_new(Mode);
432 new->id = id;
433 new->enter = enter;
434 new->leave = leave;
435 new->keypress = keypress;
436 new->input_changed = input_changed;
437 new->flags = 0;
438
439 /* Initialize the hashmap if this was not done before */
440 if (!vb.modes) {
441 vb.modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)mode_free);
442 }
443 g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new);
444 }
445
vb_mode_handle_key(Client * c,int key)446 VbResult vb_mode_handle_key(Client *c, int key)
447 {
448 VbResult res;
449
450 if (c->state.ctrlv) {
451 c->state.processed_key = FALSE;
452 c->state.ctrlv = FALSE;
453
454 return RESULT_COMPLETE;
455 }
456 if (c->mode->id != 'p' && key == CTRL('V')) {
457 c->mode->flags |= FLAG_NOMAP;
458 c->state.ctrlv = TRUE;
459
460 return RESULT_MORE;
461 }
462
463 if (c->mode && c->mode->keypress) {
464 #ifdef DEBUGDISABLED
465 int flags = c->mode->flags;
466 int id = c->mode->id;
467 res = c->mode->keypress(c, key);
468 if (c->mode) {
469 PRINT_DEBUG(
470 "%c[%d]: %#.2x '%c' -> %c[%d]",
471 id - ' ', flags, key, (key >= 0x20 && key <= 0x7e) ? key : ' ',
472 c->mode->id - ' ', c->mode->flags
473 );
474 }
475 #else
476 res = c->mode->keypress(c, key);
477 #endif
478 return res;
479 }
480 return RESULT_ERROR;
481 }
482
483 /**
484 * Change the label for the current mode in inputbox or on the left of
485 * statusbar if inputbox is in autohide mode.
486 */
vb_modelabel_update(Client * c,const char * label)487 void vb_modelabel_update(Client *c, const char *label)
488 {
489 if (c->config.input_autohide) {
490 /* if the inputbox is potentially not shown write mode into statusbar */
491 gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label);
492 } else {
493 vb_echo(c, MSG_NORMAL, FALSE, "%s", label);
494 }
495 }
496
497 /**
498 * Close the given client instances window.
499 */
vb_quit(Client * c,gboolean force)500 gboolean vb_quit(Client *c, gboolean force)
501 {
502 /* if not forced quit - don't quit if there are still running downloads */
503 if (!force && c->state.downloads) {
504 vb_echo_force(c, MSG_ERROR, TRUE, "Can't quit: there are running downloads. Use :q! to force quit");
505 return FALSE;
506 }
507
508 /* Don't run the quit synchronously, because this could lead to access of
509 * no more existing widget where some command response is written. */
510 g_idle_add((GSourceFunc)quit, c);
511
512 return TRUE;
513 }
514
515 /**
516 * Adds content to a named register.
517 */
vb_register_add(Client * c,char buf,const char * value)518 void vb_register_add(Client *c, char buf, const char *value)
519 {
520 char *mark;
521 int idx;
522
523 if (!c->state.enable_register || !buf) {
524 return;
525 }
526
527 /* make sure the mark is a valid mark char */
528 if ((mark = strchr(REG_CHARS, buf))) {
529 /* get the index of the mark char */
530 idx = mark - REG_CHARS;
531
532 OVERWRITE_STRING(c->state.reg[idx], value);
533 }
534 }
535
536 /**
537 * Lookup register entry by it's name.
538 */
vb_register_get(Client * c,char buf)539 const char *vb_register_get(Client *c, char buf)
540 {
541 char *mark;
542 int idx;
543
544 /* make sure the mark is a valid mark char */
545 if ((mark = strchr(REG_CHARS, buf))) {
546 /* get the index of the mark char */
547 idx = mark - REG_CHARS;
548
549 return c->state.reg[idx];
550 }
551
552 return NULL;
553 }
554
statusbar_update_downloads(Client * c,GString * status)555 static void statusbar_update_downloads(Client *c, GString *status)
556 {
557 GList *list;
558 guint list_length, remaining_max = 0;
559 gdouble progress, elapsed, total, remaining;
560 WebKitDownload *download;
561
562 g_assert(c);
563 g_assert(status);
564
565 if (c->state.downloads) {
566 list_length = g_list_length(c->state.downloads);
567 g_assert(list_length);
568
569 /* get highest ETA value of all downloads based on each download's
570 * current progress fraction and time elapsed */
571 for (list = c->state.downloads; list != NULL; list = list->next) {
572 download = (WebKitDownload *)list->data;
573 g_assert(download);
574
575 progress = webkit_download_get_estimated_progress(download);
576
577 /* avoid dividing by zero */
578 if (progress == 0.0) {
579 continue;
580 }
581
582 elapsed = webkit_download_get_elapsed_time(download);
583 total = (1.0 / progress) * elapsed;
584 remaining = total - elapsed;
585
586 remaining_max = MAX(remaining, remaining_max);
587 }
588
589 g_string_append_printf(status, " %d %s (ETA %us)",
590 list_length, list_length == 1? "dnld" : "dnlds", remaining_max);
591 }
592 }
593
vb_statusbar_update(Client * c)594 void vb_statusbar_update(Client *c)
595 {
596 GString *status;
597
598 if (!gtk_widget_get_visible(GTK_WIDGET(c->statusbar.box))) {
599 return;
600 }
601
602 status = g_string_new("");
603
604 /* show the number of matches search results */
605 if (c->state.search.matches) {
606 g_string_append_printf(status, " (%d)", c->state.search.matches);
607 }
608
609 /* show load status of page or the downloads */
610 if (c->state.progress != 100) {
611 #ifdef FEATURE_WGET_PROGRESS_BAR
612 char bar[PROGRESS_BAR_LEN + 1];
613 int i, state;
614
615 state = c->state.progress * PROGRESS_BAR_LEN / 100;
616 for (i = 0; i < state; i++) {
617 bar[i] = PROGRESS_BAR[0];
618 }
619 bar[i++] = PROGRESS_BAR[1];
620 for (; i < PROGRESS_BAR_LEN; i++) {
621 bar[i] = PROGRESS_BAR[2];
622 }
623 bar[i] = '\0';
624 g_string_append_printf(status, " [%s]", bar);
625 #else
626 g_string_append_printf(status, " [%i%%]", c->state.progress);
627 #endif
628 }
629
630 statusbar_update_downloads(c, status);
631
632 /* show the scroll status */
633 if (c->state.scroll_max == 0) {
634 g_string_append(status, " All");
635 } else if (c->state.scroll_percent == 0) {
636 g_string_append(status, " Top");
637 } else if (c->state.scroll_percent == 100) {
638 g_string_append(status, " Bot");
639 } else {
640 g_string_append_printf(status, " %d%%", c->state.scroll_percent);
641 }
642
643 gtk_label_set_text(GTK_LABEL(c->statusbar.right), status->str);
644 g_string_free(status, TRUE);
645 }
646
647 /**
648 * Show the given url on the left of statusbar.
649 */
vb_statusbar_show_hover_url(Client * c,VbLinkType type,const char * uri)650 void vb_statusbar_show_hover_url(Client *c, VbLinkType type, const char *uri)
651 {
652 char *sanitized_uri,
653 *msg;
654 const char *type_label;
655
656 /* No uri given - show the current URI. */
657 if (!uri || !*uri) {
658 update_urlbar(c);
659 return;
660 }
661
662 switch (type) {
663 case LINK_TYPE_LINK:
664 type_label = "Link: ";
665 break;
666 case LINK_TYPE_IMAGE:
667 type_label = "Image: ";
668 break;
669 default:
670 return;
671 }
672
673 sanitized_uri = util_sanitize_uri(uri);
674 msg = g_strconcat(type_label, uri, NULL);
675 gtk_label_set_text(GTK_LABEL(c->statusbar.left), msg);
676 g_free(msg);
677 g_free(sanitized_uri);
678 }
679
680 /**
681 * Destroys given client and removed it from client queue. If no client is
682 * there in queue, quit the gtk main loop.
683 */
client_destroy(Client * c)684 static void client_destroy(Client *c)
685 {
686 Client *p;
687 webkit_web_view_stop_loading(c->webview);
688
689 /* Write last URL into file for recreation.
690 * The URL is only stored if the closed-max-items is not 0 and the file
691 * exists. */
692 if (c->state.uri && vb.config.closed_max && vb.files[FILES_CLOSED]) {
693 util_file_prepend_line(vb.files[FILES_CLOSED], c->state.uri,
694 vb.config.closed_max);
695 }
696
697 gtk_widget_destroy(c->window);
698
699 /* Look for the client in the list, if we searched through the list and
700 * didn't find it the client must be the first item. */
701 for (p = vb.clients; p && p->next != c; p = p->next);
702 if (p) {
703 p->next = c->next;
704 } else {
705 vb.clients = c->next;
706 }
707
708 if (c->state.search.last_query) {
709 g_free(c->state.search.last_query);
710 }
711
712 completion_cleanup(c);
713 map_cleanup(c);
714 register_cleanup(c);
715 setting_cleanup(c);
716 #ifdef FEATURE_AUTOCMD
717 autocmd_cleanup(c);
718 #endif
719 handler_free(c->handler);
720 shortcut_free(c->config.shortcuts);
721
722 g_slice_free(Client, c);
723
724 /* if there are no clients - quit the main loop */
725 if (!vb.clients) {
726 gtk_main_quit();
727 }
728 }
729
730 /**
731 * Creates a new client instance with it's own window.
732 *
733 * @webview: Related webview or NULL if a client with an independent
734 * webview shoudl be created.
735 */
client_new(WebKitWebView * webview)736 static Client *client_new(WebKitWebView *webview)
737 {
738 Client *c;
739
740 /* create the client */
741 /* Prepend the new client to the queue of clients. */
742 c = g_slice_new0(Client);
743 c->next = vb.clients;
744 vb.clients = c;
745
746 c->state.progress = 100;
747 c->config.shortcuts = shortcut_new();
748
749 completion_init(c);
750 map_init(c);
751 c->handler = handler_new();
752 #ifdef FEATURE_AUTOCMD
753 autocmd_init(c);
754 #endif
755
756 /* webview */
757 c->webview = webview_new(c, webview);
758 c->finder = webkit_web_view_get_find_controller(c->webview);
759 g_signal_connect(c->finder, "counted-matches", G_CALLBACK(on_counted_matches), c);
760
761 c->page_id = webkit_web_view_get_page_id(c->webview);
762 c->inspector = webkit_web_view_get_inspector(c->webview);
763
764 return c;
765 }
766
client_show(WebKitWebView * webview,Client * c)767 static void client_show(WebKitWebView *webview, Client *c)
768 {
769 GtkWidget *box;
770 char *xid;
771
772 c->window = create_window(c);
773
774 /* statusbar */
775 c->statusbar.box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
776 c->statusbar.mode = gtk_label_new(NULL);
777 c->statusbar.left = gtk_label_new(NULL);
778 c->statusbar.right = gtk_label_new(NULL);
779 c->statusbar.cmd = gtk_label_new(NULL);
780 gtk_widget_set_name(GTK_WIDGET(c->statusbar.box), "statusbar");
781 gtk_label_set_ellipsize(GTK_LABEL(c->statusbar.left), PANGO_ELLIPSIZE_MIDDLE);
782 gtk_widget_set_halign(c->statusbar.left, GTK_ALIGN_START);
783 gtk_widget_set_halign(c->statusbar.mode, GTK_ALIGN_START);
784
785 gtk_box_pack_start(c->statusbar.box, c->statusbar.mode, FALSE, TRUE, 0);
786 gtk_box_pack_start(c->statusbar.box, c->statusbar.left, TRUE, TRUE, 2);
787 gtk_box_pack_start(c->statusbar.box, c->statusbar.cmd, FALSE, FALSE, 0);
788 gtk_box_pack_start(c->statusbar.box, c->statusbar.right, FALSE, FALSE, 2);
789
790 /* inputbox */
791 c->input = gtk_text_view_new();
792 gtk_widget_set_name(c->input, "input");
793 c->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(c->input));
794 g_signal_connect(c->buffer, "changed", G_CALLBACK(on_textbuffer_changed), c);
795 /* Make sure the user can see the typed text. */
796 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(c->input), GTK_WRAP_WORD_CHAR);
797
798 /* pack the parts together */
799 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
800 gtk_container_add(GTK_CONTAINER(c->window), box);
801 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->webview), TRUE, TRUE, 0);
802 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->statusbar.box), FALSE, FALSE, 0);
803 gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(c->input), FALSE, FALSE, 0);
804
805 /* Set the default style for statusbar and inputbox. */
806 gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)),
807 GTK_STYLE_PROVIDER(vb.style_provider),
808 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
809 gtk_style_context_add_provider(gtk_widget_get_style_context(c->input),
810 GTK_STYLE_PROVIDER(vb.style_provider),
811 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
812
813 /* TODO separate initialization o setting from applying the values or
814 * allow to se the default values for different scopes. For now we can
815 * init the settings not in client_new because we need the access to some
816 * widget for some settings. */
817 setting_init(c);
818
819 gtk_widget_show_all(c->window);
820 if (vb.embed) {
821 xid = g_strdup_printf("%d", (int)vb.embed);
822 } else {
823 xid = g_strdup_printf("%d", (int)GDK_WINDOW_XID(gtk_widget_get_window(c->window)));
824 }
825
826 /* set the x window id to env */
827 g_setenv("VIMB_XID", xid, TRUE);
828 g_free(xid);
829
830 /* start client in normal mode */
831 vb_enter(c, 'n');
832
833 c->state.enable_register = TRUE;
834
835 /* read the config file */
836 ex_run_file(c, vb.files[FILES_CONFIG]);
837 }
838
create_window(Client * c)839 static GtkWidget *create_window(Client *c)
840 {
841 GtkWidget *window;
842
843 if (vb.embed) {
844 window = gtk_plug_new(vb.embed);
845 } else {
846 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
847 gtk_window_set_role(GTK_WINDOW(window), PROJECT_UCFIRST);
848 gtk_window_set_default_size(GTK_WINDOW(window), WIN_WIDTH, WIN_HEIGHT);
849 if (!vb.no_maximize) {
850 gtk_window_maximize(GTK_WINDOW(window));
851 }
852 }
853
854 g_object_connect(
855 G_OBJECT(window),
856 "signal::destroy", G_CALLBACK(on_window_destroy), c,
857 "signal::delete-event", G_CALLBACK(on_window_delete_event), c,
858 "signal::key-press-event", G_CALLBACK(on_map_keypress), c,
859 NULL);
860
861 return window;
862 }
863
864 /**
865 * Callback that clear the input box after a timeout if this was set on
866 * input_print.
867 */
input_clear(Client * c)868 static gboolean input_clear(Client *c)
869 {
870 if (!gtk_widget_is_focus(GTK_WIDGET(c->input))) {
871 return FALSE;
872 }
873 input_print(c, MSG_NORMAL, FALSE, "");
874
875 return FALSE;
876 }
877
878 /**
879 * Print a message to the input box.
880 *
881 * @type: Type of message normal or error
882 * @hide: If TRUE the inputbox is cleared after a short timeout.
883 * @message: The message to print.
884 */
input_print(Client * c,MessageType type,gboolean hide,const char * message)885 static void input_print(Client *c, MessageType type, gboolean hide,
886 const char *message)
887 {
888 /* apply input style only if the message type was changed */
889 if (type != c->state.input_type) {
890 c->state.input_type = type;
891 vb_input_update_style(c);
892 }
893
894 vb_input_set_text(c, message);
895
896 if (hide) {
897 /* add timeout function */
898 c->state.input_timer = g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)input_clear, c);
899 } else if (c->state.input_timer > 0) {
900 /* If there is already a timeout function but the input box content is
901 * changed - remove the timeout. Seems the user started another
902 * command or typed into inputbox. */
903 g_source_remove(c->state.input_timer);
904 c->state.input_timer = 0;
905 }
906 }
907
908 /**
909 * Tests if a path is likely intended to be an URI (given that it's not a file
910 * path or containing "://").
911 */
is_plausible_uri(const char * path)912 static gboolean is_plausible_uri(const char *path)
913 {
914 const char *i, *j;
915 if (strchr(path, ' ')) {
916 return FALSE;
917 }
918 if (strchr(path, '.')) {
919 return TRUE;
920 }
921 if ((i = strstr(path, "localhost")) &&
922 (i == path || i[-1] == '/' || i[-1] == '@') &&
923 (i[9] == 0 || i[9] == '/' || i[9] == ':')
924 ) {
925 return TRUE;
926 }
927 return (i = strchr(path, '[')) && (j = strchr(i, ':')) && strchr(j, ']');
928 }
929
930 /**
931 * Reinitializes or clears the set page marks.
932 */
marks_clear(Client * c)933 static void marks_clear(Client *c)
934 {
935 int i;
936
937 /* init empty marks array */
938 for (i = 0; i < MARK_SIZE; i++) {
939 c->state.marks[i] = -1;
940 }
941 }
942
943 /**
944 * Free the memory of given mode. This is used as destroy function of the
945 * modes hashmap.
946 */
mode_free(Mode * mode)947 static void mode_free(Mode *mode)
948 {
949 g_slice_free(Mode, mode);
950 }
951
952 /**
953 * The ::changed signal is emitted when the content of a GtkTextBuffer has
954 * changed. This call back function is connected to the input box' text buffer.
955 */
on_textbuffer_changed(GtkTextBuffer * textbuffer,gpointer user_data)956 static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data)
957 {
958 gchar *text;
959 GtkTextIter start, end;
960 Client *c = (Client *)user_data;
961
962 g_assert(c);
963
964 /* don't observe changes in completion mode */
965 if (c->mode->flags & FLAG_COMPLETION) {
966 return;
967 }
968
969 /* don't process changes not typed by the user */
970 if (gtk_widget_is_focus(c->input) && c->mode && c->mode->input_changed) {
971
972 gtk_text_buffer_get_bounds(textbuffer, &start, &end);
973 text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
974
975 c->mode->input_changed(c, text);
976
977 g_free(text);
978 }
979 }
980
981 /**
982 * Set the style of the statusbar.
983 */
set_statusbar_style(Client * c,StatusType type)984 static void set_statusbar_style(Client *c, StatusType type)
985 {
986 GtkStyleContext *ctx;
987 /* Do nothing if the new to set style is the same as the current. */
988 if (type == c->state.status_type) {
989 return;
990 }
991
992 ctx = gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box));
993
994 if (type == STATUS_SSL_VALID) {
995 gtk_style_context_remove_class(ctx, "unsecure");
996 gtk_style_context_add_class(ctx, "secure");
997 } else if (type == STATUS_SSL_INVALID) {
998 gtk_style_context_remove_class(ctx, "secure");
999 gtk_style_context_add_class(ctx, "unsecure");
1000 } else {
1001 gtk_style_context_remove_class(ctx, "secure");
1002 gtk_style_context_remove_class(ctx, "unsecure");
1003 }
1004 c->state.status_type = type;
1005 }
1006
1007 /**
1008 * Update the window title of the main window.
1009 */
set_title(Client * c,const char * title)1010 static void set_title(Client *c, const char *title)
1011 {
1012 OVERWRITE_STRING(c->state.title, title);
1013 update_title(c);
1014 g_setenv("VIMB_TITLE", title ? title : "", TRUE);
1015 }
1016
1017 /**
1018 * Spawns a new browser instance for given uri.
1019 *
1020 * @uri: URI used for the new instance.
1021 */
spawn_new_instance(const char * uri)1022 static void spawn_new_instance(const char *uri)
1023 {
1024 guint i = 0;
1025 /* memory allocation */
1026 char **cmd = g_malloc_n(
1027 3 /* basename + uri + ending NULL */
1028 + (vb.configfile ? 2 : 0)
1029 #ifndef FEATURE_NO_XEMBED
1030 + (vb.embed ? 2 : 0)
1031 #endif
1032 + (vb.incognito ? 1 : 0)
1033 + (vb.profile ? 2 : 0)
1034 + (vb.no_maximize ? 1 : 0)
1035 + g_slist_length(vb.cmdargs) * 2,
1036 sizeof(char *)
1037 );
1038
1039 cmd[i++] = vb.argv0;
1040
1041 if (vb.configfile) {
1042 cmd[i++] = "-c";
1043 cmd[i++] = vb.configfile;
1044 }
1045 #ifndef FEATURE_NO_XEMBED
1046 if (vb.embed) {
1047 char xid[64];
1048 cmd[i++] = "-e";
1049 snprintf(xid, LENGTH(xid), "%d", (int)vb.embed);
1050 cmd[i++] = xid;
1051 }
1052 #endif
1053 if (vb.incognito) {
1054 cmd[i++] = "-i";
1055 }
1056 if (vb.profile) {
1057 cmd[i++] = "-p";
1058 cmd[i++] = vb.profile;
1059 }
1060 if (vb.no_maximize) {
1061 cmd[i++] = "--no-maximize";
1062 }
1063 for (GSList *l = vb.cmdargs; l; l = l->next) {
1064 cmd[i++] = "-C";
1065 cmd[i++] = l->data;
1066 }
1067 cmd[i++] = (char*)uri;
1068 cmd[i++] = NULL;
1069
1070 /* spawn a new browser instance */
1071 g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1072
1073 /* free commandline */
1074 g_free(cmd);
1075 }
1076
1077 /**
1078 * Callback for the web contexts download-started signal.
1079 */
on_webctx_download_started(WebKitWebContext * webctx,WebKitDownload * download,Client * c)1080 static void on_webctx_download_started(WebKitWebContext *webctx,
1081 WebKitDownload *download, Client *c)
1082 {
1083 #ifdef FEATURE_AUTOCMD
1084 const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1085 autocmd_run(c, AU_DOWNLOAD_STARTED, uri, NULL);
1086 #endif
1087
1088 if (GET_BOOL(c, "download-use-external")) {
1089 g_signal_connect(download, "notify::response", G_CALLBACK(on_webdownload_response_received), c);
1090 } else {
1091 g_signal_connect(download, "decide-destination", G_CALLBACK(on_webdownload_decide_destination), c);
1092 g_signal_connect(download, "failed", G_CALLBACK(on_webdownload_failed), c);
1093 g_signal_connect(download, "finished", G_CALLBACK(on_webdownload_finished), c);
1094 g_signal_connect(download, "received-data", G_CALLBACK(on_webdownload_received_data), c);
1095
1096 c->state.downloads = g_list_append(c->state.downloads, download);
1097
1098 /* to reflect the correct download count */
1099 vb_statusbar_update(c);
1100 }
1101 }
1102
1103 /**
1104 * Callback for the web contexts initialize-web-extensions signal.
1105 */
on_webctx_init_web_extension(WebKitWebContext * webctx,gpointer data)1106 static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data)
1107 {
1108 const char *name;
1109 GVariant *vdata;
1110
1111 #if (CHECK_WEBEXTENSION_ON_STARTUP)
1112 char *extension = g_build_filename(EXTENSIONDIR, "webext_main.so", NULL);
1113 if (!g_file_test(extension, G_FILE_TEST_IS_REGULAR)) {
1114 g_error("Cannot access web extension %s", extension);
1115 }
1116 g_free(extension);
1117 #endif
1118
1119 name = ext_proxy_init();
1120 vdata = g_variant_new("(ms)", name);
1121 webkit_web_context_set_web_extensions_initialization_user_data(webctx, vdata);
1122
1123 /* Setup the extension directory. */
1124 webkit_web_context_set_web_extensions_directory(webctx, EXTENSIONDIR);
1125 }
1126
1127 /**
1128 * Callback for the webkit download decide destination signal.
1129 * This signal is emitted after response is received to decide a destination
1130 * URI for the download.
1131 */
on_webdownload_decide_destination(WebKitDownload * download,gchar * suggested_filename,Client * c)1132 static gboolean on_webdownload_decide_destination(WebKitDownload *download,
1133 gchar *suggested_filename, Client *c)
1134 {
1135 if (webkit_download_get_destination(download)) {
1136 return TRUE;
1137 }
1138
1139 return vb_download_set_destination(c, download, suggested_filename, NULL);
1140 }
1141
on_webdownload_response_received(WebKitDownload * download,GParamSpec * ps,Client * c)1142 static void on_webdownload_response_received(WebKitDownload *download,
1143 GParamSpec *ps, Client *c)
1144 {
1145 spawn_download_command(c, webkit_download_get_response(download));
1146 webkit_download_cancel(download);
1147 }
1148
spawn_download_command(Client * c,WebKitURIResponse * response)1149 static void spawn_download_command(Client *c, WebKitURIResponse *response)
1150 {
1151 char *cmd;
1152 char **argv, **envp;
1153 int argc;
1154 GError *error = NULL;
1155
1156 cmd = g_strdup_printf(GET_CHAR(c, "download-command"),
1157 webkit_uri_response_get_uri(response));
1158
1159 if (!g_shell_parse_argv(cmd, &argc, &argv, &error)) {
1160 g_warning("Could not parse download-command '%s': %s",
1161 cmd,
1162 error->message);
1163 g_error_free(error);
1164 g_free(cmd);
1165 return;
1166 }
1167
1168 envp = g_get_environ();
1169 envp = g_environ_setenv(envp, "VIMB_DOWNLOAD_PATH",
1170 GET_CHAR(c, "download-path"), TRUE);
1171
1172 if (g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
1173 vb_echo(c, MSG_NORMAL, FALSE, "Download started");
1174 } else {
1175 vb_echo(c, MSG_ERROR, TRUE, "Could not start download");
1176 g_warning("%s", error->message);
1177 g_clear_error(&error);
1178 }
1179 g_free(cmd);
1180 g_strfreev(envp);
1181 g_strfreev(argv);
1182 }
1183
1184 /**
1185 * Callback for the webkit download failed signal.
1186 * This signal is emitted when an error occurs during the download operation.
1187 */
on_webdownload_failed(WebKitDownload * download,GError * error,Client * c)1188 static void on_webdownload_failed(WebKitDownload *download,
1189 GError *error, Client *c)
1190 {
1191 gchar *destination = NULL, *filename = NULL, *basename = NULL;
1192
1193 g_assert(download);
1194 g_assert(error);
1195 g_assert(c);
1196
1197 #ifdef FEATURE_AUTOCMD
1198 const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1199 autocmd_run(c, AU_DOWNLOAD_FAILED, uri, NULL);
1200 #endif
1201
1202 /* get the failed download's destination uri */
1203 g_object_get(download, "destination", &destination, NULL);
1204 g_assert(destination);
1205
1206 /* filename from uri */
1207 if (destination) {
1208 filename = g_filename_from_uri(destination, NULL, NULL);
1209 g_free(destination);
1210 }
1211
1212 /* basename from filename */
1213 if (filename) {
1214 basename = g_path_get_basename(filename);
1215 g_free(filename);
1216 }
1217
1218 /* report the error to the user */
1219 if (basename) {
1220 vb_echo(c, MSG_ERROR, FALSE, "Download of %s failed (%s)", basename, error->message);
1221 g_free(basename);
1222 }
1223 }
1224
1225 /**
1226 * Callback for the webkit download finished signal.
1227 * This signal is emitted when download finishes successfully or due to an
1228 * error. In case of errors “failed” signal is emitted before this one.
1229 */
on_webdownload_finished(WebKitDownload * download,Client * c)1230 static void on_webdownload_finished(WebKitDownload *download, Client *c)
1231 {
1232 gchar *destination = NULL, *filename = NULL, *basename = NULL;
1233
1234 g_assert(download);
1235 g_assert(c);
1236
1237 #ifdef FEATURE_AUTOCMD
1238 const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1239 autocmd_run(c, AU_DOWNLOAD_FINISHED, uri, NULL);
1240 #endif
1241
1242 c->state.downloads = g_list_remove(c->state.downloads, download);
1243
1244 /* to reflect the correct download count */
1245 vb_statusbar_update(c);
1246
1247 /* get the finished downloads destination uri */
1248 g_object_get(download, "destination", &destination, NULL);
1249 g_assert(destination);
1250
1251 /* filename from uri */
1252 if (destination) {
1253 filename = g_filename_from_uri(destination, NULL, NULL);
1254 g_free(destination);
1255 }
1256
1257 if (filename) {
1258 /* basename from filename */
1259 basename = g_path_get_basename(filename);
1260
1261 if (basename) {
1262 /* Only report to the user if the downloaded file exists, so the
1263 * download was successful. Otherwise, this is a failed download
1264 * finished signal and it was reported to the user in
1265 * on_webdownload_failed() already. */
1266 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1267 vb_echo(c, MSG_NORMAL, FALSE, "Download of %s finished", basename);
1268 }
1269
1270 g_free(basename);
1271 }
1272
1273 g_free(filename);
1274 }
1275 }
1276
1277 /**
1278 * Callback for the webkit download received-data signal.
1279 * This signal is emitted after response is received, every time new data has
1280 * been written to the destination. It's useful to know the progress of the
1281 * download operation.
1282 */
on_webdownload_received_data(WebKitDownload * download,guint64 data_length,Client * c)1283 static void on_webdownload_received_data(WebKitDownload *download,
1284 guint64 data_length, Client *c)
1285 {
1286 /* rate limit statusbar updates */
1287 static gint64 statusbar_update_next = 0;
1288
1289 if (g_get_monotonic_time() > statusbar_update_next) {
1290 statusbar_update_next = g_get_monotonic_time() + 1000000; /* 1 second */
1291
1292 vb_statusbar_update(c);
1293 }
1294 }
1295
1296 /**
1297 * Callback for the webview close signal.
1298 */
on_webview_close(WebKitWebView * webview,Client * c)1299 static void on_webview_close(WebKitWebView *webview, Client *c)
1300 {
1301 gtk_widget_destroy(c->window);
1302 }
1303
1304 /**
1305 * Callback for the webview create signal.
1306 * This creates a new client - with it's own window with a related webview.
1307 */
on_webview_create(WebKitWebView * webview,WebKitNavigationAction * navact,Client * c)1308 static WebKitWebView *on_webview_create(WebKitWebView *webview,
1309 WebKitNavigationAction *navact, Client *c)
1310 {
1311 WebKitURIRequest *req;
1312 if (c->config.prevent_newwindow) {
1313 req = webkit_navigation_action_get_request(navact);
1314 vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
1315
1316 return NULL;
1317 }
1318
1319 Client *new = client_new(webview);
1320
1321 return new->webview;
1322 }
1323
1324 /**
1325 * Callback for the webview decide-policy signal.
1326 * Checks the reasons for some navigation actions and decides if the action is
1327 * allowed, or should go into a new instance of vimb.
1328 */
on_webview_decide_policy(WebKitWebView * webview,WebKitPolicyDecision * dec,WebKitPolicyDecisionType type,Client * c)1329 static gboolean on_webview_decide_policy(WebKitWebView *webview,
1330 WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c)
1331 {
1332 switch (type) {
1333 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1334 decide_navigation_action(c, dec);
1335 break;
1336
1337 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1338 decide_new_window_action(c, dec);
1339 break;
1340
1341 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1342 decide_response(c, dec);
1343 break;
1344
1345 default:
1346 webkit_policy_decision_ignore(dec);
1347 break;
1348 }
1349
1350 return TRUE;
1351 }
1352
decide_navigation_action(Client * c,WebKitPolicyDecision * dec)1353 static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec)
1354 {
1355 guint button, mod;
1356 WebKitNavigationAction *a;
1357 WebKitURIRequest *req;
1358 const char *uri;
1359
1360 a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
1361 req = webkit_navigation_action_get_request(a);
1362 uri = webkit_uri_request_get_uri(req);
1363
1364 /* Try to handle with specific protocol handler. */
1365 if (handler_handle_uri(c->handler, uri)) {
1366 webkit_policy_decision_ignore(dec);
1367 return;
1368 }
1369
1370 button = webkit_navigation_action_get_mouse_button(a);
1371 mod = webkit_navigation_action_get_modifiers(a);
1372 /* Spawn new instance if the new win flag is set on the mode, or the
1373 * navigation was triggered by CTRL-LeftMouse or MiddleMouse. */
1374 if ((c->mode->flags & FLAG_NEW_WIN)
1375 || (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
1376 && (button == 2 || (button == 1 && mod & GDK_CONTROL_MASK)))) {
1377
1378 /* Remove the FLAG_NEW_WIN after the first use. */
1379 c->mode->flags &= ~FLAG_NEW_WIN;
1380
1381 webkit_policy_decision_ignore(dec);
1382 spawn_new_instance(uri);
1383 } else {
1384 #ifdef FEATURE_AUTOCMD
1385 if (strcmp(uri, "about:blank"))
1386 autocmd_run(c, AU_LOAD_STARTING, uri, NULL);
1387 #endif
1388 webkit_policy_decision_use(dec);
1389 }
1390 }
1391
decide_new_window_action(Client * c,WebKitPolicyDecision * dec)1392 static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec)
1393 {
1394 WebKitNavigationAction *a;
1395 WebKitURIRequest *req;
1396
1397 a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
1398
1399 switch (webkit_navigation_action_get_navigation_type(a)) {
1400 case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1401 case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1402 case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1403 case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1404 case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1405 /* This is triggered on link click for links with target="_blank".
1406 * Maybe it should be configurable if the page is opened as tab or
1407 * a new instance. Ignore opening new window if this was started
1408 * without user gesture. */
1409 if (webkit_navigation_action_is_user_gesture(a)) {
1410 req = webkit_navigation_action_get_request(a);
1411 if (c->config.prevent_newwindow) {
1412 /* Load the uri into the browser instance. */
1413 vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
1414 } else {
1415 spawn_new_instance(webkit_uri_request_get_uri(req));
1416 }
1417 }
1418 break;
1419
1420 case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1421 default:
1422 break;
1423 }
1424 webkit_policy_decision_ignore(dec);
1425 }
1426
decide_response(Client * c,WebKitPolicyDecision * dec)1427 static void decide_response(Client *c, WebKitPolicyDecision *dec)
1428 {
1429 guint status;
1430 WebKitURIResponse *res;
1431
1432 res = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(dec));
1433 status = webkit_uri_response_get_status_code(res);
1434
1435 if (webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(dec))) {
1436 webkit_policy_decision_use(dec);
1437 } else if (SOUP_STATUS_IS_SUCCESSFUL(status) || status == SOUP_STATUS_NONE) {
1438 webkit_policy_decision_download(dec);
1439 } else {
1440 webkit_policy_decision_ignore(dec);
1441 }
1442 }
1443
on_webview_load_changed(WebKitWebView * webview,WebKitLoadEvent event,Client * c)1444 static void on_webview_load_changed(WebKitWebView *webview,
1445 WebKitLoadEvent event, Client *c)
1446 {
1447 GTlsCertificateFlags tlsflags;
1448 const char *raw_uri;
1449 char *uri = NULL;
1450
1451 raw_uri = webkit_web_view_get_uri(webview);
1452 if (raw_uri) {
1453 uri = util_sanitize_uri(raw_uri);
1454 }
1455
1456 switch (event) {
1457 case WEBKIT_LOAD_STARTED:
1458 #ifdef FEATURE_AUTOCMD
1459 autocmd_run(c, AU_LOAD_STARTED, raw_uri, NULL);
1460 #endif
1461 /* update load progress in statusbar */
1462 c->state.progress = 0;
1463 vb_statusbar_update(c);
1464 if (uri) {
1465 set_title(c, uri);
1466 }
1467 /* Make sure hinting is cleared before the new page is loaded.
1468 * Without that vimb would still be in hinting mode after hinting
1469 * was started and some links was clicked my mouse. Even if there
1470 * could not hints be shown. */
1471 if (c->mode->flags & FLAG_HINTING) {
1472 vb_enter(c, 'n');
1473 }
1474 break;
1475
1476 case WEBKIT_LOAD_REDIRECTED:
1477 break;
1478
1479 case WEBKIT_LOAD_COMMITTED:
1480 /* In case of HTTP authentication request we ignore the focus
1481 * changes so that the input mode can be set for the
1482 * authentication request. If the authentication dialog is filled
1483 * or aborted the load will be commited. So this seems to be the
1484 * right place to remove the flag. */
1485 c->mode->flags &= ~FLAG_IGNORE_FOCUS;
1486 #ifdef FEATURE_AUTOCMD
1487 autocmd_run(c, AU_LOAD_COMMITTED, raw_uri, NULL);
1488 #endif
1489 /* save the current URI in register % */
1490 vb_register_add(c, '%', uri);
1491 /* check if tls is on and the page is trusted */
1492 if (uri && g_str_has_prefix(uri, "https://")) {
1493 if (webkit_web_view_get_tls_info(webview, NULL, &tlsflags) && tlsflags) {
1494 set_statusbar_style(c, STATUS_SSL_INVALID);
1495 } else {
1496 set_statusbar_style(c, STATUS_SSL_VALID);
1497 }
1498 } else {
1499 set_statusbar_style(c, STATUS_NORMAL);
1500 }
1501
1502 /* clear possible set marks */
1503 marks_clear(c);
1504
1505 /* Unset possible last search. Use commit==TRUE to clear inputbox
1506 * in case a link was fired from highlighted link. */
1507 command_search(c, &(Arg){0, NULL}, TRUE);
1508
1509 break;
1510
1511 case WEBKIT_LOAD_FINISHED:
1512 #ifdef FEATURE_AUTOCMD
1513 autocmd_run(c, AU_LOAD_FINISHED, raw_uri, NULL);
1514 #endif
1515 c->state.progress = 100;
1516 if (uri && strncmp(uri, "about:", 6)) {
1517 history_add(c, HISTORY_URL, uri, webkit_web_view_get_title(webview));
1518 }
1519 break;
1520 }
1521
1522 if (uri) {
1523 g_free(uri);
1524 }
1525 }
1526
1527 /**
1528 * Callback for the webview mouse-target-changed signal.
1529 * This is used to print the uri too statusbar if the user hovers over links
1530 * or images.
1531 */
on_webview_mouse_target_changed(WebKitWebView * webview,WebKitHitTestResult * result,guint modifiers,Client * c)1532 static void on_webview_mouse_target_changed(WebKitWebView *webview,
1533 WebKitHitTestResult *result, guint modifiers, Client *c)
1534 {
1535 /* Save the hitTestResult to have this later available for events that
1536 * don't support this. */
1537 if (c->state.hit_test_result) {
1538 g_object_unref(c->state.hit_test_result);
1539 }
1540 c->state.hit_test_result = g_object_ref(result);
1541
1542 if (webkit_hit_test_result_context_is_link(result)) {
1543 vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
1544 webkit_hit_test_result_get_link_uri(result));
1545 } else if (webkit_hit_test_result_context_is_image(result)) {
1546 vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
1547 webkit_hit_test_result_get_image_uri(result));
1548 } else {
1549 /* No link under cursor - show the current URI. */
1550 vb_statusbar_show_hover_url(c, LINK_TYPE_NONE, NULL);
1551 }
1552 }
1553
1554 /**
1555 * Called on webviews notify::estimated-load-progress event. This writes the
1556 * esitamted load progress in percent in a variable and updates the statusbar
1557 * to make the changes visible.
1558 */
on_webview_notify_estimated_load_progress(WebKitWebView * webview,GParamSpec * spec,Client * c)1559 static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
1560 GParamSpec *spec, Client *c)
1561 {
1562 c->state.progress = webkit_web_view_get_estimated_load_progress(webview) * 100;
1563 vb_statusbar_update(c);
1564 update_title(c);
1565 }
1566
1567 /**
1568 * Callback for the webview notify::title signal.
1569 * Changes the window title according to the title of the current page.
1570 */
on_webview_notify_title(WebKitWebView * webview,GParamSpec * pspec,Client * c)1571 static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec, Client *c)
1572 {
1573 const char *title = webkit_web_view_get_title(webview);
1574
1575 if (*title) {
1576 set_title(c, title);
1577 }
1578 }
1579
1580 /**
1581 * Callback for the webview notify::uri signal.
1582 * Changes the current uri shown on left of statusbar.
1583 */
on_webview_notify_uri(WebKitWebView * webview,GParamSpec * pspec,Client * c)1584 static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec, Client *c)
1585 {
1586 if (c->state.uri) {
1587 g_free(c->state.uri);
1588 }
1589
1590 c->state.uri = util_sanitize_uri(webkit_web_view_get_uri(c->webview));
1591
1592 update_urlbar(c);
1593 g_setenv("VIMB_URI", c->state.uri, TRUE);
1594 }
1595
1596 /**
1597 * Callback for the webview ready-to-show signal.
1598 * Show the webview only if it's ready to be shown.
1599 */
on_webview_ready_to_show(WebKitWebView * webview,Client * c)1600 static void on_webview_ready_to_show(WebKitWebView *webview, Client *c)
1601 {
1602 client_show(webview, c);
1603 }
1604
1605 /**
1606 * Callback for the webview web-process-crashed signal.
1607 */
on_webview_web_process_crashed(WebKitWebView * webview,Client * c)1608 static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c)
1609 {
1610 vb_echo(c, MSG_ERROR, FALSE, "Webview Crashed on %s", webkit_web_view_get_uri(webview));
1611
1612 return TRUE;
1613 }
1614
1615 /**
1616 * Callback in case HTTP authentication is requested by the server.
1617 */
on_webview_authenticate(WebKitWebView * webview,WebKitAuthenticationRequest * request,Client * c)1618 static gboolean on_webview_authenticate(WebKitWebView *webview,
1619 WebKitAuthenticationRequest *request, Client *c)
1620 {
1621 /* Don't change the mode if we are in pass through mode. */
1622 if (c->mode->id == 'n') {
1623 vb_enter(c, 'i');
1624 /* Make sure we do not switch back to normal mode in case a previos
1625 * page is open and looses the focus. */
1626 c->mode->flags |= FLAG_IGNORE_FOCUS;
1627 }
1628 return FALSE;
1629 }
1630
1631 /**
1632 * Callback in case JS calls element.webkitRequestFullScreen.
1633 */
on_webview_enter_fullscreen(WebKitWebView * webview,Client * c)1634 static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c)
1635 {
1636 c->state.is_fullscreen = TRUE;
1637 gtk_widget_hide(GTK_WIDGET(c->statusbar.box));
1638 gtk_widget_set_visible(GTK_WIDGET(c->input), FALSE);
1639 return FALSE;
1640 }
1641
1642 /**
1643 * Callback to restore the window state after entering fullscreen.
1644 */
on_webview_leave_fullscreen(WebKitWebView * webview,Client * c)1645 static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c)
1646 {
1647 c->state.is_fullscreen = FALSE;
1648 gtk_widget_show(GTK_WIDGET(c->statusbar.box));
1649 gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE);
1650 return FALSE;
1651 }
1652
1653 /**
1654 * Callback for window ::delete-event signal which is emitted if a user
1655 * requests that a toplevel window is closed. The default handler for this
1656 * signal destroys the window. Returns TRUE to stop other handlers from being
1657 * invoked for the event. FALSE to propagate the event further.
1658 */
on_window_delete_event(GtkWidget * window,GdkEvent * event,Client * c)1659 static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c)
1660 {
1661 /* if vb_quit fails, do not propagate event further, keep window open */
1662 return !vb_quit(c, FALSE);
1663 }
1664
1665 /**
1666 * Callback for the window destroy signal.
1667 * Destroys the client that is associated to the window.
1668 */
on_window_destroy(GtkWidget * window,Client * c)1669 static void on_window_destroy(GtkWidget *window, Client *c)
1670 {
1671 client_destroy(c);
1672 }
1673
1674 /**
1675 * Callback for to quit given client as idle event source.
1676 */
quit(Client * c)1677 static gboolean quit(Client *c)
1678 {
1679 /* Destroy the main window to tirgger the destruction of the client. */
1680 gtk_widget_destroy(c->window);
1681
1682 /* Remove this from the list of event sources. */
1683 return FALSE;
1684 }
1685
1686 /**
1687 * Read string from stdin and pass it to webkit for html interpretation.
1688 */
read_from_stdin(Client * c)1689 static void read_from_stdin(Client *c)
1690 {
1691 GIOChannel *ch;
1692 gchar *buf = NULL;
1693 GError *err = NULL;
1694 gsize len = 0;
1695
1696 g_assert(c);
1697
1698 ch = g_io_channel_unix_new(fileno(stdin));
1699 g_io_channel_read_to_end(ch, &buf, &len, &err);
1700 g_io_channel_unref(ch);
1701
1702 if (err) {
1703 g_warning("Error loading from stdin: %s", err->message);
1704 g_error_free(err);
1705 } else {
1706 webkit_web_view_load_html(c->webview, buf, NULL);
1707 }
1708 g_free(buf);
1709 }
1710
1711 /**
1712 * Free the register contents memory.
1713 */
register_cleanup(Client * c)1714 static void register_cleanup(Client *c)
1715 {
1716 int i;
1717 for (i = 0; i < REG_SIZE; i++) {
1718 if (c->state.reg[i]) {
1719 g_free(c->state.reg[i]);
1720 }
1721 }
1722 }
1723
update_title(Client * c)1724 static void update_title(Client *c)
1725 {
1726 #ifdef FEATURE_TITLE_PROGRESS
1727 /* Show load status of page or the downloads. */
1728 if (c->state.progress != 100) {
1729 char *title = g_strdup_printf(
1730 "[%i%%] %s",
1731 c->state.progress,
1732 c->state.title ? c->state.title : "");
1733 gtk_window_set_title(GTK_WINDOW(c->window), title);
1734 g_free(title);
1735
1736 return;
1737 }
1738 #endif
1739 if (c->state.title) {
1740 gtk_window_set_title(GTK_WINDOW(c->window), c->state.title);
1741 }
1742 }
1743
1744 /**
1745 * Update the contents of the url bar on the left of the statu bar according
1746 * to current opened url and position in back forward history.
1747 */
update_urlbar(Client * c)1748 static void update_urlbar(Client *c)
1749 {
1750 GString *str;
1751 gboolean back, fwd;
1752
1753 str = g_string_new("");
1754 /* show profile name */
1755 if (vb.profile) {
1756 g_string_append_printf(str, "[%s] ", vb.profile);
1757 }
1758
1759 g_string_append_printf(str, "%s", c->state.uri);
1760
1761 /* show history indicator only if there is something to show */
1762 back = webkit_web_view_can_go_back(c->webview);
1763 fwd = webkit_web_view_can_go_forward(c->webview);
1764 if (back || fwd) {
1765 g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+");
1766 }
1767
1768 gtk_label_set_text(GTK_LABEL(c->statusbar.left), str->str);
1769 g_string_free(str, TRUE);
1770 }
1771
1772 #ifdef FREE_ON_QUIT
1773 /**
1774 * Free memory of the whole application.
1775 */
vimb_cleanup(void)1776 static void vimb_cleanup(void)
1777 {
1778 int i;
1779
1780 while (vb.clients) {
1781 client_destroy(vb.clients);
1782 }
1783
1784 /* free memory of other components */
1785 util_cleanup();
1786
1787 for (i = 0; i < STORAGE_LAST; i++) {
1788 file_storage_free(vb.storage[i]);
1789 }
1790 for (i = 0; i < FILES_LAST; i++) {
1791 if (vb.files[i]) {
1792 g_free(vb.files[i]);
1793 }
1794 }
1795 g_free(vb.profile);
1796
1797 g_slist_free_full(vb.cmdargs, g_free);
1798 }
1799 #endif
1800
1801 /**
1802 * Setup resources used on application scope.
1803 */
vimb_setup(void)1804 static void vimb_setup(void)
1805 {
1806 WebKitWebContext *ctx;
1807 WebKitCookieManager *cm;
1808 char *path;
1809
1810 /* prepare the file pathes */
1811 path = util_get_config_dir();
1812
1813 if (vb.configfile) {
1814 char *rp = realpath(vb.configfile, NULL);
1815 vb.files[FILES_CONFIG] = g_strdup(rp);
1816 free(rp);
1817 } else {
1818 vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL);
1819 }
1820
1821 /* Setup those files that are use multiple time during runtime */
1822 if (!vb.incognito) {
1823 vb.files[FILES_CLOSED] = g_build_filename(path, "closed", NULL);
1824 vb.files[FILES_COOKIE] = g_build_filename(path, "cookies.db", NULL);
1825 }
1826 vb.files[FILES_BOOKMARK] = g_build_filename(path, "bookmark", NULL);
1827 vb.files[FILES_QUEUE] = g_build_filename(path, "queue", NULL);
1828 vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL);
1829 vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL);
1830
1831 vb.storage[STORAGE_HISTORY] = file_storage_new(path, "history", vb.incognito);
1832 vb.storage[STORAGE_COMMAND] = file_storage_new(path, "command", vb.incognito);
1833 vb.storage[STORAGE_SEARCH] = file_storage_new(path, "search", vb.incognito);
1834 g_free(path);
1835
1836 /* Use seperate rendering processed for the webview of the clients in the
1837 * current instance. This must be called as soon as possible according to
1838 * the documentation. */
1839 if (vb.incognito) {
1840 ctx = webkit_web_context_new_ephemeral();
1841 } else {
1842 ctx = webkit_web_context_get_default();
1843 }
1844 webkit_web_context_set_process_model(ctx, WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1845 webkit_web_context_set_cache_model(ctx, WEBKIT_CACHE_MODEL_WEB_BROWSER);
1846
1847 g_signal_connect(ctx, "initialize-web-extensions", G_CALLBACK(on_webctx_init_web_extension), NULL);
1848
1849 /* Add cookie support only if the cookie file exists. */
1850 if (vb.files[FILES_COOKIE]) {
1851 cm = webkit_web_context_get_cookie_manager(ctx);
1852 webkit_cookie_manager_set_persistent_storage(
1853 cm,
1854 vb.files[FILES_COOKIE],
1855 WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
1856 }
1857
1858 /* initialize the modes */
1859 vb_mode_add('n', normal_enter, normal_leave, normal_keypress, NULL);
1860 vb_mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed);
1861 vb_mode_add('i', input_enter, input_leave, input_keypress, NULL);
1862 vb_mode_add('p', pass_enter, pass_leave, pass_keypress, NULL);
1863
1864 /* Prepare the style provider to be used for the clients and completion. */
1865 vb.style_provider = gtk_css_provider_new();
1866 }
1867
1868 /**
1869 * Update the gui style settings for client c, given a style setting name and a
1870 * style setting value to be updated. The complete style sheet document will be
1871 * regenerated and re-fed into gtk css provider.
1872 */
vb_gui_style_update(Client * c,const char * setting_name_new,const char * setting_value_new)1873 void vb_gui_style_update(Client *c, const char *setting_name_new, const char *setting_value_new)
1874 {
1875 g_assert(c);
1876 g_assert(setting_name_new);
1877 g_assert(setting_value_new);
1878
1879 /* The css style sheet document being composed in this function */
1880 GString *style_sheet = g_string_new(GUI_STYLE_CSS_BASE);
1881 size_t i;
1882
1883 /* Mapping from vimb config setting name to css style sheet string */
1884 static const char *setting_style_map[][2] = {
1885 {"completion-css", " #completion{%s}"},
1886 {"completion-hover-css", " #completion:hover{%s}"},
1887 {"completion-selected-css", " #completion:selected{%s}"},
1888 {"input-css", " #input{%s}"},
1889 {"input-error-css", " #input.error{%s}"},
1890 {"status-css", " #statusbar{%s}"},
1891 {"status-ssl-css", " #statusbar.secure{%s}"},
1892 {"status-ssl-invalid-css", " #statusbar.unsecure{%s}"},
1893 {0, 0},
1894 };
1895
1896 /* For each supported style setting name */
1897 for (i = 0; setting_style_map[i][0]; i++) {
1898 const char *setting_name = setting_style_map[i][0];
1899 const char *style_string = setting_style_map[i][1];
1900
1901 /* If the current style setting name is the one to be updated,
1902 * append the given value with appropriate css wrapping to the
1903 * style sheet document. */
1904 if (strcmp(setting_name, setting_name_new) == 0) {
1905 if (strlen(setting_value_new)) {
1906 g_string_append_printf(style_sheet, style_string, setting_value_new);
1907 }
1908 }
1909 /* If the current style setting name is NOT the one being updated,
1910 * append the css string based on the current config setting. */
1911 else {
1912 Setting* setting_value = (Setting*)g_hash_table_lookup(c->config.settings, setting_name);
1913
1914 /* If the current style setting name is not available via settings
1915 * yet - this happens during setting_init() - cleanup and return.
1916 * We are going to be called again. With the last setting_add(),
1917 * all style setting names are available. */
1918 if(!setting_value) {
1919 goto cleanup;
1920 }
1921
1922 if (strlen(setting_value->value.s)) {
1923 g_string_append_printf(style_sheet, style_string, setting_value->value.s);
1924 }
1925 }
1926 }
1927
1928 /* Feed style sheet document to gtk */
1929 gtk_css_provider_load_from_data(vb.style_provider, style_sheet->str, -1, NULL);
1930
1931 /* WORKAROUND to always ensure correct size of input field
1932 *
1933 * The following line is required to apply the style defined font size on
1934 * the GtkTextView c->input. Without the call, the font size is updated on
1935 * first user input, leading to a sudden unpleasant widget size and layout
1936 * change. According to the GTK+ docs, this call should not be required as
1937 * style context invalidation is automatic.
1938 *
1939 * "gtk_style_context_invalidate has been deprecated since version 3.12
1940 * and should not be used in newly-written code. Style contexts are
1941 * invalidated automatically."
1942 * https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-style-context-invalidate
1943 *
1944 * Required settings in vimb config file:
1945 * set input-autohide=true
1946 * set input-font-normal=20pt monospace
1947 *
1948 * A bug has been filed at GTK+
1949 * https://bugzilla.gnome.org/show_bug.cgi?id=781158
1950 *
1951 * Tested on ARCH linux with gtk3 3.22.10-1
1952 */
1953 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
1954 gtk_style_context_invalidate(gtk_widget_get_style_context(c->input));
1955 G_GNUC_END_IGNORE_DEPRECATIONS;
1956
1957 cleanup:
1958 g_string_free(style_sheet, TRUE);
1959 }
1960
1961 /**
1962 * Factory to create a new webview.
1963 *
1964 * @webview: Relates webview or NULL. If given a related webview is
1965 * generated.
1966 */
webview_new(Client * c,WebKitWebView * webview)1967 static WebKitWebView *webview_new(Client *c, WebKitWebView *webview)
1968 {
1969 WebKitWebView *new;
1970 WebKitUserContentManager *ucm;
1971 WebKitWebContext *webcontext;
1972
1973 /* create a new webview */
1974 ucm = webkit_user_content_manager_new();
1975 if (webview) {
1976 new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webview));
1977 } else {
1978 new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(ucm));
1979 }
1980
1981 g_object_connect(
1982 G_OBJECT(new),
1983 "signal::close", G_CALLBACK(on_webview_close), c,
1984 "signal::create", G_CALLBACK(on_webview_create), c,
1985 "signal::decide-policy", G_CALLBACK(on_webview_decide_policy), c,
1986 "signal::load-changed", G_CALLBACK(on_webview_load_changed), c,
1987 "signal::mouse-target-changed", G_CALLBACK(on_webview_mouse_target_changed), c,
1988 "signal::notify::estimated-load-progress", G_CALLBACK(on_webview_notify_estimated_load_progress), c,
1989 "signal::notify::title", G_CALLBACK(on_webview_notify_title), c,
1990 "signal::notify::uri", G_CALLBACK(on_webview_notify_uri), c,
1991 "signal::permission-request", G_CALLBACK(on_permission_request), c,
1992 "signal::ready-to-show", G_CALLBACK(on_webview_ready_to_show), c,
1993 "signal::web-process-crashed", G_CALLBACK(on_webview_web_process_crashed), c,
1994 "signal::authenticate", G_CALLBACK(on_webview_authenticate), c,
1995 "signal::enter-fullscreen", G_CALLBACK(on_webview_enter_fullscreen), c,
1996 "signal::leave-fullscreen", G_CALLBACK(on_webview_leave_fullscreen), c,
1997 NULL
1998 );
1999
2000 webcontext = webkit_web_view_get_context(new);
2001 g_signal_connect(webcontext, "download-started", G_CALLBACK(on_webctx_download_started), c);
2002
2003 /* Setup script message handlers. */
2004 webkit_user_content_manager_register_script_message_handler(ucm, "focus");
2005 g_signal_connect(ucm, "script-message-received::focus", G_CALLBACK(on_script_message_focus), NULL);
2006
2007 return new;
2008 }
2009
on_counted_matches(WebKitFindController * finder,guint count,Client * c)2010 static void on_counted_matches(WebKitFindController *finder, guint count, Client *c)
2011 {
2012 c->state.search.matches = count;
2013 vb_statusbar_update(c);
2014 }
2015
on_permission_request(WebKitWebView * webview,WebKitPermissionRequest * request,Client * c)2016 static gboolean on_permission_request(WebKitWebView *webview,
2017 WebKitPermissionRequest *request, Client *c)
2018 {
2019 GtkWidget *dialog;
2020 int result;
2021 char *msg = NULL;
2022
2023 if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
2024 char* geolocation_setting = GET_CHAR(c, "geolocation");
2025 if (strcmp(geolocation_setting, "ask") == 0) {
2026 msg = "access your location";
2027 } else if (strcmp(geolocation_setting, "always") == 0) {
2028 webkit_permission_request_allow(request);
2029 return TRUE;
2030 } else if (strcmp(geolocation_setting, "never") == 0) {
2031 webkit_permission_request_deny(request);
2032 return TRUE;
2033 }
2034 } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
2035 if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
2036 msg = "access the microphone";
2037 } else if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
2038 msg = "access you webcam";
2039 }
2040 } else {
2041 return FALSE;
2042 }
2043
2044 dialog = gtk_message_dialog_new(GTK_WINDOW(c->window), GTK_DIALOG_MODAL,
2045 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Page wants to %s",
2046 msg);
2047
2048 gtk_widget_show(dialog);
2049 result = gtk_dialog_run(GTK_DIALOG(dialog));
2050 if (GTK_RESPONSE_YES == result) {
2051 webkit_permission_request_allow(request);
2052 } else {
2053 webkit_permission_request_deny(request);
2054 }
2055 gtk_widget_destroy(dialog);
2056
2057 return TRUE;
2058 }
2059
on_script_message_focus(WebKitUserContentManager * manager,WebKitJavascriptResult * res,gpointer data)2060 static void on_script_message_focus(WebKitUserContentManager *manager,
2061 WebKitJavascriptResult *res, gpointer data)
2062 {
2063 char *message;
2064 GVariant *variant;
2065 guint64 pageid;
2066 gboolean is_focused;
2067 Client *c;
2068
2069 message = util_js_result_as_string(res);
2070 variant = g_variant_parse(G_VARIANT_TYPE("(tb)"), message, NULL, NULL, NULL);
2071 g_free(message);
2072
2073 g_variant_get(variant, "(tb)", &pageid, &is_focused);
2074 g_variant_unref(variant);
2075
2076 c = vb_get_client_for_page_id(pageid);
2077 if (!c || c->mode->flags & FLAG_IGNORE_FOCUS) {
2078 return;
2079 }
2080
2081 /* Don't change the mode if we are in pass through mode. */
2082 if (c->mode->id == 'n' && is_focused) {
2083 vb_enter(c, 'i');
2084 } else if (c->mode->id == 'i' && !is_focused) {
2085 vb_enter(c, 'n');
2086 }
2087 }
2088
profileOptionArgFunc(const gchar * option_name,const gchar * value,gpointer data,GError ** error)2089 static gboolean profileOptionArgFunc(const gchar *option_name,
2090 const gchar *value, gpointer data, GError **error)
2091 {
2092 vb.profile = util_sanitize_filename(g_strdup(value));
2093
2094 return TRUE;
2095 }
2096
autocmdOptionArgFunc(const gchar * option_name,const gchar * value,gpointer data,GError ** error)2097 static gboolean autocmdOptionArgFunc(const gchar *option_name,
2098 const gchar *value, gpointer data, GError **error)
2099 {
2100 vb.cmdargs = g_slist_append(vb.cmdargs, g_strdup(value));
2101 return TRUE;
2102 }
2103
main(int argc,char * argv[])2104 int main(int argc, char* argv[])
2105 {
2106 Client *c;
2107 GError *err = NULL;
2108 char *pidstr, *winid = NULL;
2109 gboolean ver = FALSE, buginfo = FALSE;
2110
2111 GOptionEntry opts[] = {
2112 {"cmd", 'C', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)autocmdOptionArgFunc, "Ex command run before first page is loaded", NULL},
2113 {"config", 'c', 0, G_OPTION_ARG_FILENAME, &vb.configfile, "Custom configuration file", NULL},
2114 {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL},
2115 {"incognito", 'i', 0, G_OPTION_ARG_NONE, &vb.incognito, "Run with user data read-only", NULL},
2116 {"profile", 'p', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)profileOptionArgFunc, "Profile name", NULL},
2117 {"version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Print version", NULL},
2118 {"no-maximize", 0, 0, G_OPTION_ARG_NONE, &vb.no_maximize, "Do no attempt to maximize window", NULL},
2119 {"bug-info", 0, 0, G_OPTION_ARG_NONE, &buginfo, "Print used library versions", NULL},
2120 {NULL}
2121 };
2122
2123 /* initialize GTK+ */
2124 if (!gtk_init_with_args(&argc, &argv, "[URI]", opts, NULL, &err)) {
2125 fprintf(stderr, "can't init gtk: %s\n", err->message);
2126 g_error_free(err);
2127
2128 return EXIT_FAILURE;
2129 }
2130
2131 if (ver) {
2132 printf("%s, version %s\n", PROJECT, VERSION);
2133 return EXIT_SUCCESS;
2134 }
2135
2136 if (buginfo) {
2137 printf("Version: %s\n", VERSION);
2138 printf("WebKit compile: %d.%d.%d\n",
2139 WEBKIT_MAJOR_VERSION,
2140 WEBKIT_MINOR_VERSION,
2141 WEBKIT_MICRO_VERSION);
2142 printf("WebKit run: %d.%d.%d\n",
2143 webkit_get_major_version(),
2144 webkit_get_minor_version(),
2145 webkit_get_micro_version());
2146 printf("GTK compile: %d.%d.%d\n",
2147 GTK_MAJOR_VERSION,
2148 GTK_MINOR_VERSION,
2149 GTK_MICRO_VERSION);
2150 printf("GTK run: %d.%d.%d\n",
2151 gtk_major_version,
2152 gtk_minor_version,
2153 gtk_micro_version);
2154 printf("libsoup compile: %d.%d.%d\n",
2155 SOUP_MAJOR_VERSION,
2156 SOUP_MINOR_VERSION,
2157 SOUP_MICRO_VERSION);
2158 printf("libsoup run: %u.%u.%u\n",
2159 soup_get_major_version(),
2160 soup_get_minor_version(),
2161 soup_get_micro_version());
2162 printf("Extension dir: %s\n",
2163 EXTENSIONDIR);
2164
2165 return EXIT_SUCCESS;
2166 }
2167
2168 /* Save the base name for spawning new instances. */
2169 vb.argv0 = argv[0];
2170
2171 /* set the current pid in env */
2172 pidstr = g_strdup_printf("%d", (int)getpid());
2173 g_setenv("VIMB_PID", pidstr, TRUE);
2174 g_free(pidstr);
2175
2176 vimb_setup();
2177
2178 if (winid) {
2179 vb.embed = strtol(winid, NULL, 0);
2180 }
2181
2182 c = client_new(NULL);
2183 client_show(NULL, c);
2184
2185 /* process the --cmd if this was given */
2186 for (GSList *l = vb.cmdargs; l; l = l->next) {
2187 ex_run_string(c, l->data, false);
2188 }
2189 if (argc <= 1) {
2190 vb_load_uri(c, &(Arg){TARGET_CURRENT, NULL});
2191 } else if (!strcmp(argv[argc - 1], "-")) {
2192 /* read from stdin if uri is - */
2193 read_from_stdin(c);
2194 } else {
2195 vb_load_uri(c, &(Arg){TARGET_CURRENT, argv[argc - 1]});
2196 }
2197
2198 gtk_main();
2199 #ifdef FREE_ON_QUIT
2200 vimb_cleanup();
2201 #endif
2202
2203 return EXIT_SUCCESS;
2204 }
2205