6 #include <webkit2/webkit2.h>
11 WebKitWebView *client_new_request(WebKitWebView *, WebKitNavigationAction *,
13 gboolean crashed_web_view(WebKitWebView *, gpointer);
14 gboolean decide_policy(WebKitWebView *, WebKitPolicyDecision *,
15 WebKitPolicyDecisionType, gpointer);
16 gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
17 gboolean key_tablabel(GtkWidget *, GdkEvent *, gpointer);
18 gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
19 gboolean key_common(GtkWidget *, GdkEvent *, gpointer);
20 gboolean key_wsearch(GtkWidget *, GdkEvent *, gpointer);
21 gboolean key_isearch(GtkWidget *, GdkEvent *, gpointer);
22 gboolean isearch_counted_matches(GtkWidget *, guint, gpointer);
23 gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
24 gchar *ensure_uri_scheme(const gchar *);
25 void changed_favicon(GObject *, GParamSpec *, gpointer);
26 void changed_load_progress(GObject *, GParamSpec *, gpointer);
27 void changed_title(GObject *, GParamSpec *, gpointer);
28 void changed_uri(GObject *, GParamSpec *, gpointer);
29 void grab_feeds_finished(GObject *, GAsyncResult *, gpointer);
30 void hover_web_view(WebKitWebView *, WebKitHitTestResult *, guint, gpointer);
31 void icon_location(GtkEntry *, GtkEntryIconPosition, GdkEvent *, gpointer);
32 void mainwindow_title(gint);
33 void notebook_switch_page(GtkNotebook *, GtkWidget *, guint, gpointer);
34 void show_web_view(WebKitWebView *, gpointer);
35 void trust_user_certs(WebKitWebContext *);
41 GtkWidget *isearch_box;
42 GtkWidget *isearch_matches;
47 WebKitSettings *settings;
48 gboolean focus_new_tab;
49 gchar *external_handler_uri;
59 struct Configuration {
60 gboolean cooperative_alone;
61 gboolean noncooperative_instances;
67 struct Client **client_arr;
69 int cooperative_pipe_fp = 0;
72 size_t num_closed = 0;
77 fprintf(stderr, "chorizo: fatal: alloc failed\n");
82 client_destroy(GtkWidget *widget, gpointer data)
84 struct Client *c = (struct Client *)data;
86 g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
87 changed_load_progress, c);
89 idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
91 fprintf(stderr, "chorizo: warning: tab index was -1\n");
93 gtk_notebook_remove_page(GTK_NOTEBOOK(mw.notebook), idx);
95 if (!cfg.private && WEBKIT_IS_WEB_VIEW(c->web_view)) {
97 webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
99 // TODO: Shift everything left if over certain amount
101 if (num_closed > cfg_max_tabs_closed) {
102 memmove(closed_tabs, closed_tabs,
103 cfg_max_tabs_closed - 1);
104 num_closed = cfg_max_tabs_closed;
108 num_closed * sizeof(closed_tabs[0]));
109 if (!closed_tabs) allocfail();
111 closed_tabs[num_closed - 1] = strdup(uri);
117 quit_if_nothing_active();
121 set_uri(const char *uri, struct Client *c)
123 if (!gtk_widget_is_focus(c->location))
124 gtk_entry_set_text(GTK_ENTRY(c->location),
125 (uri != NULL) ? uri : "");
129 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show,
134 GtkWidget *evbox, *tabbox;
135 if (uri != NULL && !cfg.noncooperative_instances &&
136 !cfg.cooperative_alone) {
137 f = ensure_uri_scheme(uri);
138 write(cooperative_pipe_fp, f, strlen(f));
139 write(cooperative_pipe_fp, "\n", 1);
143 c = calloc(1, sizeof(struct Client));
145 c->focus_new_tab = focus_tab;
147 if (related_wv == NULL) {
148 WebKitUserContentManager *ucm =
149 webkit_user_content_manager_new();
150 WebKitUserScript *wkscript;
151 WebKitUserStyleSheet *wkstyle;
152 gchar *path = NULL, *source, *base;
153 const gchar *entry = NULL;
155 base = g_build_filename(g_get_user_data_dir(), "chorizo",
156 "user-scripts", NULL);
157 dir = g_dir_open(base, 0, NULL);
159 while ((entry = g_dir_read_name(dir)) != NULL) {
160 path = g_build_filename(base, entry, NULL);
161 if (g_str_has_suffix(path, ".js")) {
162 g_file_get_contents(path, &source, NULL,
164 wkscript = webkit_user_script_new(
166 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
167 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
169 webkit_user_content_manager_add_script(
171 webkit_user_script_unref(wkscript);
174 if (source) g_free(source);
178 base = g_build_filename(g_get_user_data_dir(), "chorizo",
179 "user-styles", NULL);
180 dir = g_dir_open(base, 0, NULL);
182 while ((entry = g_dir_read_name(dir)) != NULL) {
183 path = g_build_filename(base, entry, NULL);
184 if (g_str_has_suffix(path, ".css")) {
185 g_file_get_contents(path, &source, NULL,
187 wkstyle = webkit_user_style_sheet_new(
189 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
190 WEBKIT_USER_STYLE_LEVEL_USER,
192 webkit_user_content_manager_add_style_sheet(
194 webkit_user_style_sheet_unref(wkstyle);
204 webkit_web_view_new_with_user_content_manager(ucm);
206 c->web_view = webkit_web_view_new_with_related_view(related_wv);
210 webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view));
212 webkit_settings_set_enable_write_console_messages_to_stdout(
214 webkit_settings_set_enable_developer_extras(c->settings, TRUE);
216 g_signal_connect(G_OBJECT(c->web_view), "notify::favicon",
217 G_CALLBACK(changed_favicon), c);
218 g_signal_connect(G_OBJECT(c->web_view), "notify::title",
219 G_CALLBACK(changed_title), c);
220 g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
221 G_CALLBACK(changed_uri), c);
222 g_signal_connect(G_OBJECT(c->web_view),
223 "notify::estimated-load-progress",
224 G_CALLBACK(changed_load_progress), c);
225 g_signal_connect(G_OBJECT(c->web_view), "create",
226 G_CALLBACK(client_new_request), NULL);
227 g_signal_connect(G_OBJECT(c->web_view), "close",
228 G_CALLBACK(client_destroy), c);
229 g_signal_connect(G_OBJECT(c->web_view), "decide-policy",
230 G_CALLBACK(decide_policy), NULL);
231 g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
232 G_CALLBACK(key_web_view), c);
233 g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
234 G_CALLBACK(key_web_view), c);
235 g_signal_connect(G_OBJECT(c->web_view), "mouse-target-changed",
236 G_CALLBACK(hover_web_view), c);
237 g_signal_connect(G_OBJECT(c->web_view), "web-process-crashed",
238 G_CALLBACK(crashed_web_view), c);
240 GtkWidget *locbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
242 c->location = gtk_entry_new();
243 gtk_entry_set_placeholder_text(GTK_ENTRY(c->location), "URL");
244 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
245 GTK_ENTRY_ICON_PRIMARY,
246 "text-x-generic-symbolic");
247 gtk_box_pack_start(GTK_BOX(locbox), c->location, TRUE, TRUE, 0);
249 c->wsearch = gtk_entry_new();
250 gtk_entry_set_placeholder_text(GTK_ENTRY(c->wsearch), "Search the web");
251 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->wsearch),
252 GTK_ENTRY_ICON_PRIMARY,
253 "system-search-symbolic");
254 gtk_box_pack_start(GTK_BOX(locbox), c->wsearch, FALSE, TRUE, 0);
257 GtkWidget *privindicator = gtk_label_new("Private mode");
258 gtk_widget_set_tooltip_text(
260 "You are in private mode. No history, caches, or "
261 "cookies will be saved beyond this session.");
262 gtk_box_pack_end(GTK_BOX(locbox), privindicator, FALSE, FALSE,
266 g_signal_connect(G_OBJECT(c->location), "key-press-event",
267 G_CALLBACK(key_location), c);
268 g_signal_connect(G_OBJECT(c->location), "icon-release",
269 G_CALLBACK(icon_location), c);
270 g_signal_connect(G_OBJECT(c->wsearch), "key-press-event",
271 G_CALLBACK(key_wsearch), c);
273 * XXX This is a workaround. Setting this to NULL (which is done in
274 * grab_feeds_finished() if no feed has been detected) adds a little
275 * padding left of the text. Not sure why. The point of this call
276 * right here is to have that padding right from the start. This
277 * avoids a graphical artifact.
280 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
281 GTK_ENTRY_ICON_SECONDARY, NULL);
283 c->isearch_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
284 c->isearch = gtk_entry_new();
285 gtk_entry_set_placeholder_text(GTK_ENTRY(c->isearch), "Search page");
286 gtk_box_pack_start(GTK_BOX(c->isearch_box), c->isearch, FALSE, TRUE, 0);
287 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->isearch),
288 GTK_ENTRY_ICON_PRIMARY,
289 "system-search-symbolic");
290 g_signal_connect(G_OBJECT(c->isearch), "key-press-event",
291 G_CALLBACK(key_isearch), c);
293 c->isearch_matches = gtk_label_new("0 matches");
294 gtk_box_pack_start(GTK_BOX(c->isearch_box), c->isearch_matches, FALSE,
297 c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
298 gtk_box_pack_start(GTK_BOX(c->vbox), c->web_view, TRUE, TRUE, 0);
299 gtk_box_pack_start(GTK_BOX(c->vbox), c->isearch_box, FALSE, TRUE, 0);
300 gtk_box_pack_start(GTK_BOX(c->vbox), locbox, FALSE, FALSE, 0);
302 gtk_container_set_focus_child(GTK_CONTAINER(c->vbox), c->web_view);
304 c->tabicon = gtk_image_new_from_icon_name("text-html",
305 GTK_ICON_SIZE_SMALL_TOOLBAR);
307 c->tablabel = gtk_label_new("chorizo");
308 gtk_label_set_ellipsize(GTK_LABEL(c->tablabel), PANGO_ELLIPSIZE_END);
309 gtk_label_set_width_chars(GTK_LABEL(c->tablabel), cfg_tab_width);
310 gtk_widget_set_has_tooltip(c->tablabel, TRUE);
313 * XXX I don't own a HiDPI screen, so I don't know if scale_factor
314 * does the right thing.
316 tabbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,
317 5 * gtk_widget_get_scale_factor(mw.win));
318 gtk_box_pack_start(GTK_BOX(tabbox), c->tabicon, FALSE, FALSE, 0);
319 gtk_box_pack_start(GTK_BOX(tabbox), c->tablabel, TRUE, TRUE, 0);
321 evbox = gtk_event_box_new();
322 gtk_container_add(GTK_CONTAINER(evbox), tabbox);
323 g_signal_connect(G_OBJECT(evbox), "button-release-event",
324 G_CALLBACK(key_tablabel), c);
326 gtk_widget_add_events(evbox, GDK_SCROLL_MASK);
327 g_signal_connect(G_OBJECT(evbox), "scroll-event",
328 G_CALLBACK(key_tablabel), c);
330 // For easy access, store a reference to our label.
331 g_object_set_data(G_OBJECT(evbox), "chorizo-tab-label", c->tablabel);
334 * This only shows the event box and the label inside, nothing else.
335 * Needed because the evbox/label is "internal" to the notebook and
336 * not part of the normal "widget tree" (IIUC).
338 gtk_widget_show_all(evbox);
340 int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)) + 1;
341 gtk_notebook_insert_page(GTK_NOTEBOOK(mw.notebook), c->vbox, evbox,
343 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(mw.notebook), c->vbox,
347 show_web_view(NULL, c);
349 g_signal_connect(G_OBJECT(c->web_view), "ready-to-show",
350 G_CALLBACK(show_web_view), c);
353 f = ensure_uri_scheme(uri);
354 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
360 client_arr = realloc(client_arr, (clients + 1) * sizeof(client_arr[0]));
361 if (!client_arr) allocfail();
363 client_arr[clients] = c;
365 if (uri == NULL) gtk_widget_grab_focus(c->location);
367 return WEBKIT_WEB_VIEW(c->web_view);
371 client_new_request(WebKitWebView *web_view,
372 WebKitNavigationAction *navigation_action, gpointer data)
374 return client_new(NULL, web_view, FALSE, FALSE);
378 mkdirp(const char *dir, mode_t mode)
383 snprintf(tmp, sizeof(tmp), "%s", dir);
385 if (tmp[len - 1] == '/') tmp[len - 1] = 0;
386 for (p = tmp + 1; *p; p++)
396 cooperation_setup(void)
401 gchar *priv = (cfg.private) ? "-private" : "";
402 const gchar *fifo_suffix_env = g_getenv("CHORIZO_FIFO_SUFFIX");
403 const gchar *fifo_suffix = (fifo_suffix_env) ? fifo_suffix_env : "";
404 fifofilename = g_strdup_printf("%s%s%s%s", "chorizo", priv, ".fifo",
406 fifopath = g_build_filename(g_get_user_runtime_dir(), "chorizo",
408 mkdirp(dirname(fifopath), 0600);
409 g_free(fifofilename);
411 if (!g_file_test(fifopath, G_FILE_TEST_EXISTS)) mkfifo(fifopath, 0600);
413 cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
414 if (!cooperative_pipe_fp) {
415 fprintf(stderr, "chorizo: error: can't open FIFO\n");
417 if (write(cooperative_pipe_fp, "", 0) == -1) {
419 * Could not do an empty write to the FIFO which
420 * means there's no one listening.
422 close(cooperative_pipe_fp);
423 towatch = g_io_channel_new_file(fifopath, "r+", NULL);
424 g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg,
427 cfg.cooperative_alone = FALSE;
433 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data)
435 struct Client *c = (struct Client *)data;
438 "a = document.querySelectorAll('"
440 "link[rel=\"alternate\"][href][type=\"application/atom+xml\"],"
442 "link[rel=\"alternate\"][href][type=\"application/rss+xml\"]"
448 " for (i = 0; i < a.length; i++) {"
449 " url = encodeURIComponent(a[i].href);"
450 " if ('title' in a[i] && a[i].title != '')"
451 " title = encodeURIComponent(a[i].title);"
454 " out += '<li><a href=\"' + url + '\">' + title + "
459 p = webkit_web_view_get_estimated_load_progress(
460 WEBKIT_WEB_VIEW(c->web_view));
465 * The page has loaded fully. We now run the short JavaScript
466 * snippet above that operates on the DOM. It tries to grab
467 * all occurences of <link rel="alternate" ...>, i.e.
468 * RSS/Atom feed references.
470 webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(c->web_view),
472 grab_feeds_finished, c);
474 gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), p);
478 changed_favicon(GObject *obj, GParamSpec *pspec, gpointer data)
480 struct Client *c = (struct Client *)data;
482 int w, h, w_should, h_should;
483 GdkPixbuf *pb, *pb_scaled;
484 f = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(c->web_view));
486 gtk_image_set_from_icon_name(GTK_IMAGE(c->tabicon), "text-html",
487 GTK_ICON_SIZE_SMALL_TOOLBAR);
489 w = cairo_image_surface_get_width(f);
490 h = cairo_image_surface_get_height(f);
491 pb = gdk_pixbuf_get_from_surface(f, 0, 0, w, h);
493 w_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
494 h_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
495 pb_scaled = gdk_pixbuf_scale_simple(
496 pb, w_should, h_should, GDK_INTERP_BILINEAR);
497 gtk_image_set_from_pixbuf(GTK_IMAGE(c->tabicon),
500 g_object_unref(pb_scaled);
507 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
510 struct Client *c = (struct Client *)data;
511 u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
512 t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
514 u = u == NULL ? "chorizo" : u;
515 u = u[0] == 0 ? "chorizo" : u;
517 t = t == NULL ? u : t;
518 t = t[0] == 0 ? u : t;
520 gchar *name = malloc(strlen(t) + 4);
521 if (!name) allocfail();
523 webkit_web_view_get_is_muted(WEBKIT_WEB_VIEW(c->web_view));
524 gchar *muted = (mute) ? "[M] " : "";
525 sprintf(name, "%s%s", muted, t);
526 gtk_label_set_text(GTK_LABEL(c->tablabel), name);
529 gtk_widget_set_tooltip_text(c->tablabel, t);
531 gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)));
535 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
538 struct Client *c = (struct Client *)data;
540 t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
542 if (t != NULL && strlen(t) > 0) {
545 // No g_get_user_state_dir unfortunately
546 gchar *state_env = getenv("XDG_STATE_DIR");
547 gchar *state_dir = (state_env) ?
549 g_build_filename(g_get_home_dir(),
553 gchar *history_file =
554 g_build_filename(state_dir, "history", NULL);
556 mkdirp(state_dir, 0700);
557 fp = fopen(history_file, "a");
559 fprintf(fp, "%s\n", t);
562 perror("chorizo: error: could not open history file");
565 g_free(history_file);
571 crashed_web_view(WebKitWebView *web_view, gpointer data)
573 struct Client *c = (struct Client *)data;
575 GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
576 GtkWidget *dialog = gtk_message_dialog_new(
577 GTK_WINDOW(mw.win), flags, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
578 "ERROR: Web process %s crashed.\n%s",
579 webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)),
581 gtk_dialog_run(GTK_DIALOG(dialog));
582 gtk_widget_destroy(dialog);
588 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
589 WebKitPolicyDecisionType type, gpointer data)
591 WebKitResponsePolicyDecision *r;
593 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
594 r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
595 if (!webkit_response_policy_decision_is_mime_type_supported(r))
596 webkit_policy_decision_download(decision);
598 webkit_policy_decision_use(decision);
601 // Use whatever default there is.
608 ensure_uri_scheme(const gchar *t)
611 f = g_ascii_strdown(t, -1);
612 if (!g_str_has_prefix(f, "http:") && !g_str_has_prefix(f, "https:") &&
613 !g_str_has_prefix(f, "file:") && !g_str_has_prefix(f, "about:") &&
614 !g_str_has_prefix(f, "data:") && !g_str_has_prefix(f, "webkit:")) {
616 fabs = realpath(t, NULL);
618 f = g_strdup_printf("file://%s", fabs);
621 f = g_strdup_printf("http://%s", t);
629 grab_feeds_finished(GObject *object, GAsyncResult *result, gpointer data)
631 struct Client *c = (struct Client *)data;
632 WebKitJavascriptResult *js_result;
634 JSCException *exception;
637 g_free(c->feed_html);
641 * This was taken almost verbatim from the example in WebKit's
644 * https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html
647 js_result = webkit_web_view_run_javascript_finish(
648 WEBKIT_WEB_VIEW(object), result, &err);
651 "chorizo: error: error running javascript: %s\n",
656 value = webkit_javascript_result_get_js_value(js_result);
657 if (jsc_value_is_string(value)) {
658 str_value = jsc_value_to_string(value);
660 jsc_context_get_exception(jsc_value_get_context(value));
661 if (exception != NULL) {
663 "chorizo: warning: error running javascript: %s\n",
664 jsc_exception_get_message(exception));
666 c->feed_html = str_value;
669 gtk_entry_set_icon_from_icon_name(
670 GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY,
671 "application-rss+xml-symbolic");
672 gtk_entry_set_icon_activatable(GTK_ENTRY(c->location),
673 GTK_ENTRY_ICON_SECONDARY, TRUE);
675 gtk_entry_set_icon_from_icon_name(
676 GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY, NULL);
679 webkit_javascript_result_unref(js_result);
683 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht,
684 guint modifiers, gpointer data)
686 struct Client *c = (struct Client *)data;
688 g_free(c->hover_uri);
690 if (webkit_hit_test_result_context_is_link(ht)) {
691 to_show = webkit_hit_test_result_get_link_uri(ht);
692 c->hover_uri = g_strdup(to_show);
694 to_show = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
698 if (!gtk_widget_is_focus(c->location)) set_uri(to_show, c);
702 icon_location(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event,
705 struct Client *c = (struct Client *)data;
707 gchar *data_template = "data:text/html,"
711 " <meta charset=\"UTF-8\">"
712 " <title>Feeds</title>"
715 " <p>Feeds found on this page:</p>"
721 if (c->feed_html != NULL) {
723 * What we're actually trying to do is show a simple HTML
724 * page that lists all the feeds on the current page. The
725 * function webkit_web_view_load_html() looks like the proper
726 * way to do that. Sad thing is, it doesn't create a history
727 * entry, but instead simply replaces the content of the
728 * current page. This is not what we want.
730 * RFC 2397 [0] defines the data URI scheme [1]. We abuse this
731 * mechanism to show my custom HTML snippet* and*create a
734 * [0]: https://tools.ietf.org/html/rfc2397 [1]:
735 * https://en.wikipedia.org/wiki/Data_URI_scheme
738 d = g_strdup_printf(data_template, c->feed_html);
739 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), d);
745 init_default_web_context(void)
748 WebKitWebContext *wc;
749 WebKitCookieManager *cm;
750 wc = (cfg.private) ? webkit_web_context_new_ephemeral() :
751 webkit_web_context_get_default();
753 p = g_build_filename(g_get_user_config_dir(), "chorizo", "adblock",
755 webkit_web_context_set_sandbox_enabled(wc, TRUE);
756 webkit_web_context_add_path_to_sandbox(wc, p, TRUE);
759 WebKitProcessModel model =
760 WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES;
761 webkit_web_context_set_process_model(wc, model);
763 p = g_build_filename(g_get_user_data_dir(), "chorizo", "web-extensions",
765 webkit_web_context_set_web_extensions_directory(wc, p);
768 char *xdg_down = getenv("XDG_DOWNLOAD_DIR");
769 g_signal_connect(G_OBJECT(wc), "download-started",
770 G_CALLBACK(download_start),
771 (xdg_down) ? xdg_down : "/var/tmp");
773 trust_user_certs(wc);
775 cm = webkit_web_context_get_cookie_manager(wc);
776 webkit_cookie_manager_set_accept_policy(cm, cfg_cookie_policy);
779 webkit_web_context_set_favicon_database_directory(wc, NULL);
781 gchar *fname = g_build_filename("/", g_get_user_data_dir(),
782 "chorizo", "cookies.db", NULL);
783 mkdirp(dirname(fname), 0700);
784 WebKitCookiePersistentStorage type =
785 WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
786 webkit_cookie_manager_set_persistent_storage(cm, fname, type);
789 webkit_web_context_set_spell_checking_enabled(wc, TRUE);
793 isearch(gpointer data, gint direction)
795 struct Client *c = (struct Client *)data;
796 WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
797 WebKitFindController *fc =
798 webkit_web_view_get_find_controller(web_view);
799 const gchar *isearch_text = gtk_entry_get_text(GTK_ENTRY(c->isearch));
800 if (isearch_text == NULL) return;
804 g_signal_connect(G_OBJECT(fc), "counted-matches",
805 G_CALLBACK(isearch_counted_matches), c);
806 webkit_find_controller_count_matches(
807 fc, isearch_text, cfg_isearch_options, G_MAXUINT);
808 webkit_find_controller_search(fc, isearch_text,
809 cfg_isearch_options, G_MAXUINT);
812 webkit_find_controller_search_next(fc);
815 webkit_find_controller_search_previous(fc);
818 webkit_find_controller_search_finish(fc);
824 isearch_init(struct Client *c, int direction)
826 if (webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view))) {
827 gtk_widget_show(c->isearch_box);
828 gtk_widget_show(c->isearch);
829 gtk_widget_grab_focus(c->isearch);
836 if (num_closed == 0) return;
837 client_new(closed_tabs[num_closed - 1], NULL, TRUE, TRUE);
839 closed_tabs = realloc(closed_tabs, num_closed * sizeof(closed_tabs[0]));
840 if (!closed_tabs) allocfail();
843 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
845 struct Client *c = (struct Client *)data;
847 if (event->type == GDK_KEY_PRESS) {
848 guint mask = gtk_accelerator_get_default_mod_mask();
849 int key = ((GdkEventKey *)event)->keyval;
850 if ((((GdkEventKey *)event)->state & mask) ==
852 const char *uri = webkit_web_view_get_uri(
853 WEBKIT_WEB_VIEW(c->web_view));
854 if (GDK_KEY_y == key) {
855 downloadmanager_show();
857 } else if (GDK_KEY_h == key) {
858 webkit_web_view_go_back(
859 WEBKIT_WEB_VIEW(c->web_view));
861 } else if (GDK_KEY_l == key) {
862 webkit_web_view_go_forward(
863 WEBKIT_WEB_VIEW(c->web_view));
865 } else if (GDK_KEY_s == key) {
866 gtk_widget_grab_focus(c->location);
868 } else if (GDK_KEY_p == key) {
869 WebKitPrintOperation *operation =
870 webkit_print_operation_new(
871 WEBKIT_WEB_VIEW(c->web_view));
872 GtkWidget *toplevel =
873 gtk_widget_get_toplevel(mw.win);
874 webkit_print_operation_run_dialog(
875 operation, GTK_WINDOW(toplevel));
877 } else if (GDK_KEY_g == key) {
879 gtk_widget_grab_focus(c->web_view);
882 GTK_ENTRY(c->location), uri);
883 webkit_web_view_run_javascript(
884 WEBKIT_WEB_VIEW(c->web_view),
885 "window.getSelection().removeAllRanges();"
886 "document.activeElement.blur();",
888 gtk_widget_hide(c->isearch_box);
889 gtk_editable_set_position(
890 GTK_EDITABLE(c->location), -1);
891 gtk_editable_set_position(
892 GTK_EDITABLE(c->wsearch), -1);
894 } else if (GDK_KEY_r == key) {
895 webkit_web_view_reload_bypass_cache(
896 WEBKIT_WEB_VIEW(c->web_view));
898 } else if (GDK_KEY_j == key) {
899 for (int i = 0; i <= cfg_scroll_lines - 1;
901 event->key.keyval = GDK_KEY_Down;
902 gdk_event_put(event);
905 } else if (GDK_KEY_k == key) {
906 for (int i = 0; i <= cfg_scroll_lines - 1;
908 event->key.keyval = GDK_KEY_Up;
909 gdk_event_put(event);
912 } else if (GDK_KEY_f == key) {
915 } else if (GDK_KEY_q == key) {
916 client_destroy(NULL, c);
918 } else if (GDK_KEY_1 == key) {
919 gtk_notebook_set_current_page(
920 GTK_NOTEBOOK(mw.notebook), 0);
922 } else if (GDK_KEY_2 == key) {
923 gtk_notebook_set_current_page(
924 GTK_NOTEBOOK(mw.notebook), 1);
926 } else if (GDK_KEY_3 == key) {
927 gtk_notebook_set_current_page(
928 GTK_NOTEBOOK(mw.notebook), 2);
930 } else if (GDK_KEY_4 == key) {
931 gtk_notebook_set_current_page(
932 GTK_NOTEBOOK(mw.notebook), 3);
934 } else if (GDK_KEY_5 == key) {
935 gtk_notebook_set_current_page(
936 GTK_NOTEBOOK(mw.notebook), 4);
938 } else if (GDK_KEY_6 == key) {
939 gtk_notebook_set_current_page(
940 GTK_NOTEBOOK(mw.notebook), 5);
942 } else if (GDK_KEY_7 == key) {
943 gtk_notebook_set_current_page(
944 GTK_NOTEBOOK(mw.notebook), 6);
946 } else if (GDK_KEY_8 == key) {
947 gtk_notebook_set_current_page(
948 GTK_NOTEBOOK(mw.notebook), 7);
950 } else if (GDK_KEY_9 == key) {
951 gtk_notebook_set_current_page(
952 GTK_NOTEBOOK(mw.notebook), 8);
954 } else if (GDK_KEY_u == key) {
955 gtk_notebook_prev_page(
956 GTK_NOTEBOOK(mw.notebook));
958 } else if (GDK_KEY_m == key) {
959 gboolean muted = webkit_web_view_get_is_muted(
960 WEBKIT_WEB_VIEW(c->web_view));
961 webkit_web_view_set_is_muted(
962 WEBKIT_WEB_VIEW(c->web_view), !muted);
963 changed_title(G_OBJECT(c->web_view), NULL, c);
965 } else if (GDK_KEY_t == key) {
966 client_new(cfg_home_uri, NULL, TRUE, TRUE);
968 } else if (GDK_KEY_bracketleft == key) {
971 } else if ((GDK_KEY_i == key) || (GDK_KEY_Tab == key)) {
972 gtk_notebook_next_page(
973 GTK_NOTEBOOK(mw.notebook));
975 } else if (GDK_KEY_d == key) {
976 gtk_widget_grab_focus(c->wsearch);
978 } else if (GDK_KEY_equal == key) {
979 now = webkit_web_view_get_zoom_level(
980 WEBKIT_WEB_VIEW(c->web_view));
981 webkit_web_view_set_zoom_level(
982 WEBKIT_WEB_VIEW(c->web_view),
985 } else if (GDK_KEY_minus == key) {
986 now = webkit_web_view_get_zoom_level(
987 WEBKIT_WEB_VIEW(c->web_view));
988 webkit_web_view_set_zoom_level(
989 WEBKIT_WEB_VIEW(c->web_view),
992 } else if (GDK_KEY_0 == key) {
993 webkit_web_view_set_zoom_level(
994 WEBKIT_WEB_VIEW(c->web_view), 1);
997 } else if ((((GdkEventKey *)event)->state & mask) ==
998 (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) {
999 if (GDK_KEY_ISO_Left_Tab == key) {
1000 gtk_notebook_prev_page(
1001 GTK_NOTEBOOK(mw.notebook));
1003 } else if (GDK_KEY_T == key) {
1013 isearch_counted_matches(GtkWidget *widget, guint matches, gpointer data)
1015 struct Client *c = (struct Client *)data;
1016 char *text = malloc(12);
1017 sprintf(text, "%d matches", matches);
1018 gtk_label_set_text(GTK_LABEL(c->isearch_matches), text);
1024 key_isearch(GtkWidget *widget, GdkEvent *event, gpointer data)
1026 struct Client *c = (struct Client *)data;
1027 if (key_common(widget, event, data)) return TRUE;
1029 if (event->type == GDK_KEY_PRESS) {
1030 int key = ((GdkEventKey *)event)->keyval;
1031 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1032 int direction = (((GdkEventKey *)event)->state &
1038 isearch(c, direction);
1040 } else if (GDK_KEY_Escape == key) {
1042 gtk_widget_hide(c->isearch_box);
1043 gtk_widget_grab_focus(c->web_view);
1050 key_wsearch(GtkWidget *widget, GdkEvent *event, gpointer data)
1052 struct Client *c = (struct Client *)data;
1053 if (key_common(widget, event, data)) return TRUE;
1055 if (event->type == GDK_KEY_PRESS) {
1056 int key = ((GdkEventKey *)event)->keyval;
1057 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1059 gtk_entry_get_text(GTK_ENTRY(c->wsearch));
1060 int len = strlen(cfg_search_engine) + strlen(t);
1061 gchar *f = malloc(len + 1);
1062 if (!f) allocfail();
1064 snprintf(f, len + 1, "%s%s", cfg_search_engine, t);
1065 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
1068 gtk_widget_grab_focus(c->web_view);
1076 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
1078 struct Client *c = (struct Client *)data;
1080 if (key_common(widget, event, data)) return TRUE;
1082 if (event->type == GDK_KEY_PRESS) {
1083 int key = ((GdkEventKey *)event)->keyval;
1084 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1085 gtk_widget_grab_focus(c->web_view);
1086 t = gtk_entry_get_text(GTK_ENTRY(c->location));
1087 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
1088 ensure_uri_scheme(t));
1090 } else if (GDK_KEY_Escape == key) {
1091 t = webkit_web_view_get_uri(
1092 WEBKIT_WEB_VIEW(c->web_view));
1093 gtk_entry_set_text(GTK_ENTRY(c->location),
1094 (t == NULL) ? "" : t);
1101 key_tablabel(GtkWidget *widget, GdkEvent *event, gpointer data)
1103 GdkScrollDirection direction;
1104 if (event->type == GDK_BUTTON_RELEASE) {
1105 switch (((GdkEventButton *)event)->button) {
1107 client_destroy(NULL, data);
1110 } else if (event->type == GDK_SCROLL) {
1111 gdk_event_get_scroll_direction(event, &direction);
1112 switch (direction) {
1114 gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
1116 case GDK_SCROLL_DOWN:
1117 gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
1128 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
1130 struct Client *c = (struct Client *)data;
1133 if (key_common(c->web_view, event, data)) return TRUE;
1135 if (event->type == GDK_KEY_PRESS) {
1136 if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape) {
1137 webkit_web_view_stop_loading(
1138 WEBKIT_WEB_VIEW(c->web_view));
1139 gtk_entry_set_progress_fraction(GTK_ENTRY(c->location),
1142 } else if (event->type == GDK_SCROLL) {
1143 event->scroll.delta_y *= cfg_scroll_lines;
1144 if (((GdkEventScroll *)event)->state & GDK_CONTROL_MASK) {
1145 gdk_event_get_scroll_deltas(event, &dx, &dy);
1146 z = webkit_web_view_get_zoom_level(
1147 WEBKIT_WEB_VIEW(c->web_view));
1149 z = dx != 0 ? 1 : z;
1150 webkit_web_view_set_zoom_level(
1151 WEBKIT_WEB_VIEW(c->web_view), z);
1159 mainwindow_setup(void)
1161 mw.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1162 gtk_window_set_default_size(GTK_WINDOW(mw.win), 800, 600);
1163 g_signal_connect(G_OBJECT(mw.win), "destroy", gtk_main_quit, NULL);
1165 gchar *priv = (cfg.private) ? "-private" : "";
1166 gchar *title = malloc(strlen(priv) + 7);
1167 if (!title) allocfail();
1168 sprintf(title, "%s%s", "chorizo", priv);
1169 gtk_window_set_title(GTK_WINDOW(mw.win), title);
1172 mw.notebook = gtk_notebook_new();
1173 gtk_notebook_set_scrollable(GTK_NOTEBOOK(mw.notebook), TRUE);
1174 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(mw.notebook), GTK_POS_LEFT);
1175 gtk_container_add(GTK_CONTAINER(mw.win), mw.notebook);
1176 g_signal_connect(G_OBJECT(mw.notebook), "switch-page",
1177 G_CALLBACK(notebook_switch_page), NULL);
1179 GtkCssProvider *css = gtk_css_provider_new();
1180 const char *css_data = "notebook header.left * { \
1183 padding-bottom: 0; \
1185 gtk_css_provider_load_from_data(css, css_data, strlen(css_data), NULL);
1186 gtk_style_context_add_provider_for_screen(
1187 gdk_screen_get_default(), GTK_STYLE_PROVIDER(css),
1188 GTK_STYLE_PROVIDER_PRIORITY_USER);
1192 mainwindow_title(gint idx)
1194 GtkWidget *child, *widg, *tablabel;
1196 child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx);
1197 if (child == NULL) return;
1199 widg = gtk_notebook_get_tab_label(GTK_NOTEBOOK(mw.notebook), child);
1200 tablabel = (GtkWidget *)g_object_get_data(G_OBJECT(widg),
1201 "chorizo-tab-label");
1202 text = gtk_label_get_text(GTK_LABEL(tablabel));
1203 gtk_window_set_title(GTK_WINDOW(mw.win), text);
1207 notebook_switch_page(GtkNotebook *nb, GtkWidget *p, guint idx, gpointer data)
1209 mainwindow_title(idx);
1213 quit_if_nothing_active(void)
1216 if (downloads == 0) {
1220 downloadmanager_show();
1226 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
1229 g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
1232 client_new(uri, NULL, TRUE, TRUE);
1239 show_web_view(WebKitWebView *web_view, gpointer data)
1241 struct Client *c = (struct Client *)data;
1245 gtk_widget_show_all(mw.win);
1247 // TODO: Fix this hack
1248 for (int i = 1; i <= clients; i++)
1249 gtk_widget_hide(client_arr[i]->isearch_box);
1250 gtk_widget_hide(c->isearch_box);
1252 if (c->focus_new_tab) {
1253 idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
1255 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook),
1258 gtk_widget_grab_focus(c->web_view);
1263 trust_user_certs(WebKitWebContext *wc)
1265 GTlsCertificate *cert;
1266 gchar *basedir, *absfile;
1269 basedir = g_build_filename(g_get_user_data_dir(), "chorizo", "certs",
1271 dir = g_dir_open(basedir, 0, NULL);
1274 file = g_dir_read_name(dir);
1275 while (file != NULL) {
1276 absfile = g_build_filename(g_get_user_data_dir(),
1277 "chorizo", "certs", file,
1279 cert = g_tls_certificate_new_from_file(absfile, NULL);
1283 "chorizo: warning: could not load trusted cert: %s\n",
1286 webkit_web_context_allow_tls_certificate_for_host(
1288 file = g_dir_read_name(dir);
1297 printf("%s %s\n", "chorizo", VERSION);
1301 main(int argc, char **argv)
1306 cfg.noncooperative_instances = FALSE;
1307 cfg.cooperative_alone = TRUE;
1308 closed_tabs = malloc(0);
1309 if (!closed_tabs) allocfail();
1311 while ((opt = getopt(argc, argv, "cpvV")) != -1) {
1314 cfg.noncooperative_instances = TRUE;
1327 "usage: chorizo [OPTION]... [URI]...\n");
1332 if (cfg.verbose) version();
1334 gtk_init(&argc, &argv);
1336 // Keep clipboard contents after program closes
1337 gtk_clipboard_store(gtk_clipboard_get_for_display(
1338 gdk_display_get_default(), GDK_SELECTION_CLIPBOARD));
1340 if (!cfg.noncooperative_instances) cooperation_setup();
1342 if (cfg.noncooperative_instances || cfg.cooperative_alone)
1343 init_default_web_context();
1345 downloadmanager_setup();
1348 client_arr = malloc(sizeof(struct Client *));
1349 if (!client_arr) allocfail();
1351 if (optind >= argc) {
1352 client_new(cfg_home_uri, NULL, TRUE, TRUE);
1354 for (i = optind; i < argc; i++)
1355 client_new(argv[i], NULL, TRUE, TRUE);
1358 if (cfg.noncooperative_instances || cfg.cooperative_alone) {