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;
68 int cooperative_pipe_fp = 0;
71 size_t num_closed = 0;
76 fprintf(stderr, "chorizo: fatal: alloc failed\n");
81 client_destroy(GtkWidget *widget, gpointer data)
83 struct Client *c = (struct Client *)data;
85 g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
86 changed_load_progress, c);
88 idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
90 fprintf(stderr, "chorizo: warning: tab index was -1\n");
92 gtk_notebook_remove_page(GTK_NOTEBOOK(mw.notebook), idx);
94 if (!cfg.private && WEBKIT_IS_WEB_VIEW(c->web_view)) {
96 webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
98 // 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;
106 closed_tabs = realloc(
108 num_closed * sizeof(closed_tabs[0]));
109 if (!closed_tabs) allocfail();
111 closed_tabs[num_closed - 1] = strdup(uri);
118 quit_if_nothing_active();
122 set_uri(const char *uri, struct Client *c)
124 if (!gtk_widget_is_focus(c->location))
125 gtk_entry_set_text(GTK_ENTRY(c->location),
126 (uri != NULL) ? uri : "");
130 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show,
135 GtkWidget *evbox, *tabbox;
136 if (uri != NULL && !cfg.noncooperative_instances &&
137 !cfg.cooperative_alone) {
138 f = ensure_uri_scheme(uri);
139 write(cooperative_pipe_fp, f, strlen(f));
140 write(cooperative_pipe_fp, "\n", 1);
144 c = calloc(1, sizeof(struct Client));
146 c->focus_new_tab = focus_tab;
148 if (related_wv == NULL) {
149 WebKitUserContentManager *ucm =
150 webkit_user_content_manager_new();
151 WebKitUserScript *wkscript;
152 WebKitUserStyleSheet *wkstyle;
153 gchar *path = NULL, *source, *base;
154 const gchar *entry = NULL;
156 base = g_build_filename(g_get_user_data_dir(), "chorizo",
157 "user-scripts", NULL);
158 dir = g_dir_open(base, 0, NULL);
160 while ((entry = g_dir_read_name(dir)) != NULL) {
161 path = g_build_filename(base, entry, NULL);
162 if (g_str_has_suffix(path, ".js")) {
163 g_file_get_contents(path, &source, NULL,
165 wkscript = webkit_user_script_new(
167 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
168 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
170 webkit_user_content_manager_add_script(
172 webkit_user_script_unref(wkscript);
175 if (source) g_free(source);
179 base = g_build_filename(g_get_user_data_dir(), "chorizo",
180 "user-styles", NULL);
181 dir = g_dir_open(base, 0, NULL);
183 while ((entry = g_dir_read_name(dir)) != NULL) {
184 path = g_build_filename(base, entry, NULL);
185 if (g_str_has_suffix(path, ".css")) {
186 g_file_get_contents(path, &source, NULL,
188 wkstyle = webkit_user_style_sheet_new(
190 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
191 WEBKIT_USER_STYLE_LEVEL_USER,
193 webkit_user_content_manager_add_style_sheet(
195 webkit_user_style_sheet_unref(wkstyle);
205 webkit_web_view_new_with_user_content_manager(ucm);
207 c->settings = webkit_web_view_get_settings(
208 WEBKIT_WEB_VIEW(c->web_view));
210 webkit_settings_set_enable_write_console_messages_to_stdout(
212 webkit_settings_set_enable_developer_extras(c->settings, TRUE);
214 c->web_view = webkit_web_view_new_with_related_view(related_wv);
217 g_signal_connect(G_OBJECT(c->web_view), "notify::favicon",
218 G_CALLBACK(changed_favicon), c);
219 g_signal_connect(G_OBJECT(c->web_view), "notify::title",
220 G_CALLBACK(changed_title), c);
221 g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
222 G_CALLBACK(changed_uri), c);
223 g_signal_connect(G_OBJECT(c->web_view),
224 "notify::estimated-load-progress",
225 G_CALLBACK(changed_load_progress), c);
226 g_signal_connect(G_OBJECT(c->web_view), "create",
227 G_CALLBACK(client_new_request), NULL);
228 g_signal_connect(G_OBJECT(c->web_view), "close",
229 G_CALLBACK(client_destroy), c);
230 g_signal_connect(G_OBJECT(c->web_view), "decide-policy",
231 G_CALLBACK(decide_policy), NULL);
232 g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
233 G_CALLBACK(key_web_view), c);
234 g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
235 G_CALLBACK(key_web_view), c);
236 g_signal_connect(G_OBJECT(c->web_view), "mouse-target-changed",
237 G_CALLBACK(hover_web_view), c);
238 g_signal_connect(G_OBJECT(c->web_view), "web-process-crashed",
239 G_CALLBACK(crashed_web_view), c);
241 GtkWidget *locbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
243 c->location = gtk_entry_new();
244 gtk_entry_set_placeholder_text(GTK_ENTRY(c->location), "URL");
245 char *location_symbol;
247 location_symbol = "security-high-symbolic";
248 gtk_entry_set_icon_tooltip_markup(
249 GTK_ENTRY(c->location), GTK_ENTRY_ICON_PRIMARY,
250 "You are in private mode. No history, caches, or "
251 "cookies will be saved beyond this session.");
253 location_symbol = "text-x-generic-symbolic";
256 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
257 GTK_ENTRY_ICON_PRIMARY,
259 gtk_box_pack_start(GTK_BOX(locbox), c->location, TRUE, TRUE, 0);
261 c->wsearch = gtk_entry_new();
262 gtk_entry_set_placeholder_text(GTK_ENTRY(c->wsearch), "Search the web");
263 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->wsearch),
264 GTK_ENTRY_ICON_PRIMARY,
265 "system-search-symbolic");
266 gtk_box_pack_start(GTK_BOX(locbox), c->wsearch, FALSE, TRUE, 0);
268 g_signal_connect(G_OBJECT(c->location), "key-press-event",
269 G_CALLBACK(key_location), c);
270 g_signal_connect(G_OBJECT(c->location), "icon-release",
271 G_CALLBACK(icon_location), c);
272 g_signal_connect(G_OBJECT(c->wsearch), "key-press-event",
273 G_CALLBACK(key_wsearch), c);
275 * XXX This is a workaround. Setting this to NULL (which is done in
276 * grab_feeds_finished() if no feed has been detected) adds a little
277 * padding left of the text. Not sure why. The point of this call
278 * right here is to have that padding right from the start. This
279 * avoids a graphical artifact.
282 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
283 GTK_ENTRY_ICON_SECONDARY, NULL);
285 c->isearch_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
286 c->isearch = gtk_entry_new();
287 gtk_entry_set_placeholder_text(GTK_ENTRY(c->isearch), "Search page");
288 gtk_box_pack_start(GTK_BOX(c->isearch_box), c->isearch, FALSE, TRUE, 0);
289 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->isearch),
290 GTK_ENTRY_ICON_PRIMARY,
291 "system-search-symbolic");
292 g_signal_connect(G_OBJECT(c->isearch), "key-press-event",
293 G_CALLBACK(key_isearch), c);
295 c->isearch_matches = gtk_label_new("0 matches");
296 gtk_box_pack_start(GTK_BOX(c->isearch_box), c->isearch_matches, FALSE,
299 c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
300 gtk_box_pack_start(GTK_BOX(c->vbox), c->web_view, TRUE, TRUE, 0);
301 gtk_box_pack_start(GTK_BOX(c->vbox), c->isearch_box, FALSE, TRUE, 0);
302 gtk_box_pack_start(GTK_BOX(c->vbox), locbox, FALSE, FALSE, 0);
304 gtk_container_set_focus_child(GTK_CONTAINER(c->vbox), c->web_view);
306 c->tabicon = gtk_image_new_from_icon_name("text-html",
307 GTK_ICON_SIZE_SMALL_TOOLBAR);
309 c->tablabel = gtk_label_new("chorizo");
310 gtk_label_set_ellipsize(GTK_LABEL(c->tablabel), PANGO_ELLIPSIZE_END);
311 gtk_label_set_width_chars(GTK_LABEL(c->tablabel), cfg_tab_width);
312 gtk_widget_set_has_tooltip(c->tablabel, TRUE);
315 * XXX I don't own a HiDPI screen, so I don't know if scale_factor
316 * does the right thing.
318 tabbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,
319 5 * gtk_widget_get_scale_factor(mw.win));
320 gtk_box_pack_start(GTK_BOX(tabbox), c->tabicon, FALSE, FALSE, 0);
321 gtk_box_pack_start(GTK_BOX(tabbox), c->tablabel, TRUE, TRUE, 0);
323 evbox = gtk_event_box_new();
324 gtk_container_add(GTK_CONTAINER(evbox), tabbox);
325 g_signal_connect(G_OBJECT(evbox), "button-release-event",
326 G_CALLBACK(key_tablabel), c);
328 gtk_widget_add_events(evbox, GDK_SCROLL_MASK);
329 g_signal_connect(G_OBJECT(evbox), "scroll-event",
330 G_CALLBACK(key_tablabel), c);
332 // For easy access, store a reference to our label.
333 g_object_set_data(G_OBJECT(evbox), "chorizo-tab-label", c->tablabel);
336 * This only shows the event box and the label inside, nothing else.
337 * Needed because the evbox/label is "internal" to the notebook and
338 * not part of the normal "widget tree" (IIUC).
340 gtk_widget_show_all(evbox);
342 int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)) + 1;
343 gtk_notebook_insert_page(GTK_NOTEBOOK(mw.notebook), c->vbox, evbox,
345 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(mw.notebook), c->vbox,
349 show_web_view(NULL, c);
351 g_signal_connect(G_OBJECT(c->web_view), "ready-to-show",
352 G_CALLBACK(show_web_view), c);
355 f = ensure_uri_scheme(uri);
356 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
363 if (uri == NULL) gtk_widget_grab_focus(c->location);
365 return WEBKIT_WEB_VIEW(c->web_view);
369 client_new_request(WebKitWebView *web_view,
370 WebKitNavigationAction *navigation_action, gpointer data)
372 return client_new(NULL, web_view, FALSE, FALSE);
376 mkdirp(const char *dir, mode_t mode)
381 snprintf(tmp, sizeof(tmp), "%s", dir);
383 if (tmp[len - 1] == '/') tmp[len - 1] = 0;
384 for (p = tmp + 1; *p; p++)
394 cooperation_setup(void)
399 gchar *priv = (cfg.private) ? "-private" : "";
400 const gchar *fifo_suffix_env = g_getenv("CHORIZO_FIFO_SUFFIX");
401 const gchar *fifo_suffix = (fifo_suffix_env) ? fifo_suffix_env : "";
402 fifofilename = g_strdup_printf("%s%s%s%s", "chorizo", priv, ".fifo",
404 fifopath = g_build_filename(g_get_user_runtime_dir(), "chorizo",
406 mkdirp(dirname(fifopath), 0600);
407 g_free(fifofilename);
409 if (!g_file_test(fifopath, G_FILE_TEST_EXISTS)) mkfifo(fifopath, 0600);
411 cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
412 if (!cooperative_pipe_fp) {
413 fprintf(stderr, "chorizo: error: can't open FIFO\n");
415 if (write(cooperative_pipe_fp, "", 0) == -1) {
417 * Could not do an empty write to the FIFO which
418 * means there's no one listening.
420 close(cooperative_pipe_fp);
421 towatch = g_io_channel_new_file(fifopath, "r+", NULL);
422 g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg,
425 cfg.cooperative_alone = FALSE;
431 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data)
433 struct Client *c = (struct Client *)data;
436 "a = document.querySelectorAll('"
438 "link[rel=\"alternate\"][href][type=\"application/atom+xml\"],"
440 "link[rel=\"alternate\"][href][type=\"application/rss+xml\"]"
446 " for (i = 0; i < a.length; i++) {"
447 " url = encodeURIComponent(a[i].href);"
448 " if ('title' in a[i] && a[i].title != '')"
449 " title = encodeURIComponent(a[i].title);"
452 " out += '<li><a href=\"' + url + '\">' + title + "
457 p = webkit_web_view_get_estimated_load_progress(
458 WEBKIT_WEB_VIEW(c->web_view));
463 * The page has loaded fully. We now run the short JavaScript
464 * snippet above that operates on the DOM. It tries to grab
465 * all occurences of <link rel="alternate" ...>, i.e.
466 * RSS/Atom feed references.
468 webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(c->web_view),
470 grab_feeds_finished, c);
472 gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), p);
476 changed_favicon(GObject *obj, GParamSpec *pspec, gpointer data)
478 struct Client *c = (struct Client *)data;
480 int w, h, w_should, h_should;
481 GdkPixbuf *pb, *pb_scaled;
482 f = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(c->web_view));
484 gtk_image_set_from_icon_name(GTK_IMAGE(c->tabicon), "text-html",
485 GTK_ICON_SIZE_SMALL_TOOLBAR);
487 w = cairo_image_surface_get_width(f);
488 h = cairo_image_surface_get_height(f);
489 pb = gdk_pixbuf_get_from_surface(f, 0, 0, w, h);
491 w_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
492 h_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
493 pb_scaled = gdk_pixbuf_scale_simple(
494 pb, w_should, h_should, GDK_INTERP_BILINEAR);
495 gtk_image_set_from_pixbuf(GTK_IMAGE(c->tabicon),
498 g_object_unref(pb_scaled);
505 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
508 struct Client *c = (struct Client *)data;
509 u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
510 t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
512 u = u == NULL ? "chorizo" : u;
513 u = u[0] == 0 ? "chorizo" : u;
515 t = t == NULL ? u : t;
516 t = t[0] == 0 ? u : t;
518 gchar *name = malloc(strlen(t) + 4);
519 if (!name) allocfail();
521 webkit_web_view_get_is_muted(WEBKIT_WEB_VIEW(c->web_view));
522 gchar *muted = (mute) ? "[M] " : "";
523 sprintf(name, "%s%s", muted, t);
524 gtk_label_set_text(GTK_LABEL(c->tablabel), name);
527 gtk_widget_set_tooltip_text(c->tablabel, t);
529 gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)));
533 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
536 struct Client *c = (struct Client *)data;
538 t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
540 if (t != NULL && strlen(t) > 0) {
543 // No g_get_user_state_dir unfortunately
544 gchar *state_env = getenv("XDG_STATE_DIR");
545 gchar *state_dir = (state_env) ?
547 g_build_filename(g_get_home_dir(),
551 gchar *history_file =
552 g_build_filename(state_dir, "history", NULL);
554 mkdirp(state_dir, 0700);
555 fp = fopen(history_file, "a");
557 fprintf(fp, "%s\n", t);
560 perror("chorizo: error: could not open history file");
563 g_free(history_file);
569 crashed_web_view(WebKitWebView *web_view, gpointer data)
571 GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
572 GtkWidget *dialog = gtk_message_dialog_new(
573 GTK_WINDOW(mw.win), flags, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
574 "ERROR: Web process %s crashed.\n%s",
575 webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)),
577 gtk_dialog_run(GTK_DIALOG(dialog));
578 gtk_widget_destroy(dialog);
584 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
585 WebKitPolicyDecisionType type, gpointer data)
587 WebKitResponsePolicyDecision *r;
589 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
590 r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
591 if (!webkit_response_policy_decision_is_mime_type_supported(r))
592 webkit_policy_decision_download(decision);
594 webkit_policy_decision_use(decision);
597 // Use whatever default there is.
604 ensure_uri_scheme(const gchar *t)
607 f = g_ascii_strdown(t, -1);
608 if (!g_str_has_prefix(f, "http:") && !g_str_has_prefix(f, "https:") &&
609 !g_str_has_prefix(f, "file:") && !g_str_has_prefix(f, "about:") &&
610 !g_str_has_prefix(f, "data:") && !g_str_has_prefix(f, "webkit:")) {
612 fabs = realpath(t, NULL);
614 f = g_strdup_printf("file://%s", fabs);
617 f = g_strdup_printf("http://%s", t);
625 grab_feeds_finished(GObject *object, GAsyncResult *result, gpointer data)
627 struct Client *c = (struct Client *)data;
628 WebKitJavascriptResult *js_result;
630 JSCException *exception;
633 g_free(c->feed_html);
637 * This was taken almost verbatim from the example in WebKit's
640 * https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html
643 js_result = webkit_web_view_run_javascript_finish(
644 WEBKIT_WEB_VIEW(object), result, &err);
647 "chorizo: error: error running javascript: %s\n",
652 value = webkit_javascript_result_get_js_value(js_result);
653 if (jsc_value_is_string(value)) {
654 str_value = jsc_value_to_string(value);
656 jsc_context_get_exception(jsc_value_get_context(value));
657 if (exception != NULL) {
659 "chorizo: warning: error running javascript: %s\n",
660 jsc_exception_get_message(exception));
662 c->feed_html = str_value;
665 gtk_entry_set_icon_from_icon_name(
666 GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY,
667 "application-rss+xml-symbolic");
668 gtk_entry_set_icon_activatable(GTK_ENTRY(c->location),
669 GTK_ENTRY_ICON_SECONDARY, TRUE);
671 gtk_entry_set_icon_from_icon_name(
672 GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY, NULL);
675 webkit_javascript_result_unref(js_result);
679 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht,
680 guint modifiers, gpointer data)
682 struct Client *c = (struct Client *)data;
684 g_free(c->hover_uri);
686 if (webkit_hit_test_result_context_is_link(ht)) {
687 to_show = webkit_hit_test_result_get_link_uri(ht);
688 c->hover_uri = g_strdup(to_show);
690 to_show = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
694 if (!gtk_widget_is_focus(c->location)) set_uri(to_show, c);
698 icon_location(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event,
701 struct Client *c = (struct Client *)data;
703 gchar *data_template = "data:text/html,"
707 " <meta charset=\"UTF-8\">"
708 " <title>Feeds</title>"
711 " <p>Feeds found on this page:</p>"
717 if (c->feed_html != NULL) {
719 * What we're actually trying to do is show a simple HTML
720 * page that lists all the feeds on the current page. The
721 * function webkit_web_view_load_html() looks like the proper
722 * way to do that. Sad thing is, it doesn't create a history
723 * entry, but instead simply replaces the content of the
724 * current page. This is not what we want.
726 * RFC 2397 [0] defines the data URI scheme [1]. We abuse this
727 * mechanism to show my custom HTML snippet* and*create a
730 * [0]: https://tools.ietf.org/html/rfc2397 [1]:
731 * https://en.wikipedia.org/wiki/Data_URI_scheme
734 d = g_strdup_printf(data_template, c->feed_html);
735 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), d);
741 init_default_web_context(void)
744 WebKitWebContext *wc;
745 WebKitCookieManager *cm;
746 wc = (cfg.private) ? webkit_web_context_new_ephemeral() :
747 webkit_web_context_get_default();
749 p = g_build_filename(g_get_user_config_dir(), "chorizo", "adblock",
751 webkit_web_context_set_sandbox_enabled(wc, TRUE);
752 webkit_web_context_add_path_to_sandbox(wc, p, TRUE);
755 webkit_web_context_set_process_model(
756 wc, WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
758 p = g_build_filename(g_get_user_data_dir(), "chorizo", "web-extensions",
760 webkit_web_context_set_web_extensions_directory(wc, p);
763 char *xdg_down = getenv("XDG_DOWNLOAD_DIR");
764 g_signal_connect(G_OBJECT(wc), "download-started",
765 G_CALLBACK(download_start),
766 (xdg_down) ? xdg_down : "/var/tmp");
768 trust_user_certs(wc);
770 cm = webkit_web_context_get_cookie_manager(wc);
771 webkit_cookie_manager_set_accept_policy(cm, cfg_cookie_policy);
774 webkit_web_context_set_favicon_database_directory(wc, NULL);
776 gchar *fname = g_build_filename("/", g_get_user_data_dir(),
777 "chorizo", "cookies.db", NULL);
778 mkdirp(dirname(fname), 0700);
779 WebKitCookiePersistentStorage type =
780 WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
781 webkit_cookie_manager_set_persistent_storage(cm, fname, type);
784 webkit_web_context_set_spell_checking_enabled(wc, TRUE);
788 isearch(gpointer data, gint direction)
790 struct Client *c = (struct Client *)data;
791 WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
792 WebKitFindController *fc =
793 webkit_web_view_get_find_controller(web_view);
794 const gchar *isearch_text = gtk_entry_get_text(GTK_ENTRY(c->isearch));
795 if (isearch_text == NULL) return;
799 g_signal_connect(G_OBJECT(fc), "counted-matches",
800 G_CALLBACK(isearch_counted_matches), c);
801 webkit_find_controller_count_matches(
802 fc, isearch_text, cfg_isearch_options, G_MAXUINT);
803 webkit_find_controller_search(fc, isearch_text,
804 cfg_isearch_options, G_MAXUINT);
807 webkit_find_controller_search_next(fc);
810 webkit_find_controller_search_previous(fc);
813 webkit_find_controller_search_finish(fc);
819 isearch_init(struct Client *c, int direction)
821 if (webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view))) {
822 gtk_widget_show(c->isearch_box);
823 gtk_widget_grab_focus(c->isearch);
830 if (num_closed == 0) return;
831 client_new(closed_tabs[num_closed - 1], NULL, TRUE, TRUE);
833 closed_tabs = realloc(closed_tabs, num_closed * sizeof(closed_tabs[0]));
834 if (!closed_tabs) allocfail();
837 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
839 struct Client *c = (struct Client *)data;
841 if (event->type == GDK_KEY_PRESS) {
842 guint mask = gtk_accelerator_get_default_mod_mask();
843 int key = ((GdkEventKey *)event)->keyval;
844 if ((((GdkEventKey *)event)->state & mask) ==
846 const char *uri = webkit_web_view_get_uri(
847 WEBKIT_WEB_VIEW(c->web_view));
848 if (GDK_KEY_y == key) {
849 downloadmanager_show();
851 } else if (GDK_KEY_h == key) {
852 webkit_web_view_go_back(
853 WEBKIT_WEB_VIEW(c->web_view));
855 } else if (GDK_KEY_l == key) {
856 webkit_web_view_go_forward(
857 WEBKIT_WEB_VIEW(c->web_view));
859 } else if (GDK_KEY_s == key) {
860 gtk_widget_grab_focus(c->location);
862 } else if (GDK_KEY_p == key) {
863 WebKitPrintOperation *operation =
864 webkit_print_operation_new(
865 WEBKIT_WEB_VIEW(c->web_view));
866 GtkWidget *toplevel =
867 gtk_widget_get_toplevel(mw.win);
868 webkit_print_operation_run_dialog(
869 operation, GTK_WINDOW(toplevel));
871 } else if (GDK_KEY_g == key) {
873 gtk_widget_grab_focus(c->web_view);
876 GTK_ENTRY(c->location), uri);
877 webkit_web_view_run_javascript(
878 WEBKIT_WEB_VIEW(c->web_view),
879 "window.getSelection().removeAllRanges();"
880 "document.activeElement.blur();",
882 gtk_widget_hide(c->isearch_box);
883 gtk_editable_set_position(
884 GTK_EDITABLE(c->location), -1);
885 gtk_editable_set_position(
886 GTK_EDITABLE(c->wsearch), -1);
888 } else if (GDK_KEY_r == key) {
889 webkit_web_view_reload_bypass_cache(
890 WEBKIT_WEB_VIEW(c->web_view));
892 } else if (GDK_KEY_j == key) {
893 for (int i = 0; i <= cfg_scroll_lines - 1;
895 event->key.keyval = GDK_KEY_Down;
896 gdk_event_put(event);
899 } else if (GDK_KEY_k == key) {
900 for (int i = 0; i <= cfg_scroll_lines - 1;
902 event->key.keyval = GDK_KEY_Up;
903 gdk_event_put(event);
906 } else if (GDK_KEY_f == key) {
909 } else if (GDK_KEY_q == key) {
910 client_destroy(NULL, c);
912 } else if (GDK_KEY_1 == key) {
913 gtk_notebook_set_current_page(
914 GTK_NOTEBOOK(mw.notebook), 0);
916 } else if (GDK_KEY_2 == key) {
917 gtk_notebook_set_current_page(
918 GTK_NOTEBOOK(mw.notebook), 1);
920 } else if (GDK_KEY_3 == key) {
921 gtk_notebook_set_current_page(
922 GTK_NOTEBOOK(mw.notebook), 2);
924 } else if (GDK_KEY_4 == key) {
925 gtk_notebook_set_current_page(
926 GTK_NOTEBOOK(mw.notebook), 3);
928 } else if (GDK_KEY_5 == key) {
929 gtk_notebook_set_current_page(
930 GTK_NOTEBOOK(mw.notebook), 4);
932 } else if (GDK_KEY_6 == key) {
933 gtk_notebook_set_current_page(
934 GTK_NOTEBOOK(mw.notebook), 5);
936 } else if (GDK_KEY_7 == key) {
937 gtk_notebook_set_current_page(
938 GTK_NOTEBOOK(mw.notebook), 6);
940 } else if (GDK_KEY_8 == key) {
941 gtk_notebook_set_current_page(
942 GTK_NOTEBOOK(mw.notebook), 7);
944 } else if (GDK_KEY_9 == key) {
945 gtk_notebook_set_current_page(
946 GTK_NOTEBOOK(mw.notebook), 8);
948 } else if (GDK_KEY_u == key) {
949 gtk_notebook_prev_page(
950 GTK_NOTEBOOK(mw.notebook));
952 } else if (GDK_KEY_m == key) {
953 gboolean muted = webkit_web_view_get_is_muted(
954 WEBKIT_WEB_VIEW(c->web_view));
955 webkit_web_view_set_is_muted(
956 WEBKIT_WEB_VIEW(c->web_view), !muted);
957 changed_title(G_OBJECT(c->web_view), NULL, c);
959 } else if (GDK_KEY_t == key) {
960 client_new(cfg_home_uri, NULL, TRUE, TRUE);
962 } else if (GDK_KEY_bracketleft == key) {
965 } else if ((GDK_KEY_i == key) || (GDK_KEY_Tab == key)) {
966 gtk_notebook_next_page(
967 GTK_NOTEBOOK(mw.notebook));
969 } else if (GDK_KEY_d == key) {
970 gtk_widget_grab_focus(c->wsearch);
972 } else if (GDK_KEY_equal == key) {
973 now = webkit_web_view_get_zoom_level(
974 WEBKIT_WEB_VIEW(c->web_view));
975 webkit_web_view_set_zoom_level(
976 WEBKIT_WEB_VIEW(c->web_view),
979 } else if (GDK_KEY_minus == key) {
980 now = webkit_web_view_get_zoom_level(
981 WEBKIT_WEB_VIEW(c->web_view));
982 webkit_web_view_set_zoom_level(
983 WEBKIT_WEB_VIEW(c->web_view),
986 } else if (GDK_KEY_0 == key) {
987 webkit_web_view_set_zoom_level(
988 WEBKIT_WEB_VIEW(c->web_view), 1);
991 } else if ((((GdkEventKey *)event)->state & mask) ==
992 (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) {
993 if (GDK_KEY_ISO_Left_Tab == key) {
994 gtk_notebook_prev_page(
995 GTK_NOTEBOOK(mw.notebook));
997 } else if (GDK_KEY_T == key) {
1007 isearch_counted_matches(GtkWidget *widget, guint matches, gpointer data)
1009 struct Client *c = (struct Client *)data;
1010 char *text = malloc(12);
1011 sprintf(text, "%d matches", matches);
1012 gtk_label_set_text(GTK_LABEL(c->isearch_matches), text);
1018 key_isearch(GtkWidget *widget, GdkEvent *event, gpointer data)
1020 struct Client *c = (struct Client *)data;
1021 if (key_common(widget, event, data)) return TRUE;
1023 if (event->type == GDK_KEY_PRESS) {
1024 int key = ((GdkEventKey *)event)->keyval;
1025 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1026 int direction = (((GdkEventKey *)event)->state &
1032 isearch(c, direction);
1034 } else if (GDK_KEY_Escape == key) {
1036 gtk_widget_hide(c->isearch_box);
1037 gtk_widget_grab_focus(c->web_view);
1044 key_wsearch(GtkWidget *widget, GdkEvent *event, gpointer data)
1046 struct Client *c = (struct Client *)data;
1047 if (key_common(widget, event, data)) return TRUE;
1049 if (event->type == GDK_KEY_PRESS) {
1050 int key = ((GdkEventKey *)event)->keyval;
1051 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1053 gtk_entry_get_text(GTK_ENTRY(c->wsearch));
1054 int len = strlen(cfg_search_engine) + strlen(t);
1055 gchar *f = malloc(len + 1);
1056 if (!f) allocfail();
1058 snprintf(f, len + 1, "%s%s", cfg_search_engine, t);
1059 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
1062 gtk_widget_grab_focus(c->web_view);
1070 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
1072 struct Client *c = (struct Client *)data;
1074 if (key_common(widget, event, data)) return TRUE;
1076 if (event->type == GDK_KEY_PRESS) {
1077 int key = ((GdkEventKey *)event)->keyval;
1078 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1079 gtk_widget_grab_focus(c->web_view);
1080 t = gtk_entry_get_text(GTK_ENTRY(c->location));
1081 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
1082 ensure_uri_scheme(t));
1084 } else if (GDK_KEY_Escape == key) {
1085 t = webkit_web_view_get_uri(
1086 WEBKIT_WEB_VIEW(c->web_view));
1087 gtk_entry_set_text(GTK_ENTRY(c->location),
1088 (t == NULL) ? "" : t);
1095 key_tablabel(GtkWidget *widget, GdkEvent *event, gpointer data)
1097 GdkScrollDirection direction;
1098 if (event->type == GDK_BUTTON_RELEASE) {
1099 switch (((GdkEventButton *)event)->button) {
1101 client_destroy(NULL, data);
1104 } else if (event->type == GDK_SCROLL) {
1105 gdk_event_get_scroll_direction(event, &direction);
1106 switch (direction) {
1108 gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
1110 case GDK_SCROLL_DOWN:
1111 gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
1122 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
1124 struct Client *c = (struct Client *)data;
1127 if (key_common(c->web_view, event, data)) return TRUE;
1129 if (event->type == GDK_KEY_PRESS) {
1130 if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape) {
1131 webkit_web_view_stop_loading(
1132 WEBKIT_WEB_VIEW(c->web_view));
1133 gtk_entry_set_progress_fraction(GTK_ENTRY(c->location),
1136 } else if (event->type == GDK_SCROLL) {
1137 event->scroll.delta_y *= cfg_scroll_lines;
1138 if (((GdkEventScroll *)event)->state & GDK_CONTROL_MASK) {
1139 gdk_event_get_scroll_deltas(event, &dx, &dy);
1140 z = webkit_web_view_get_zoom_level(
1141 WEBKIT_WEB_VIEW(c->web_view));
1143 z = dx != 0 ? 1 : z;
1144 webkit_web_view_set_zoom_level(
1145 WEBKIT_WEB_VIEW(c->web_view), z);
1153 mainwindow_setup(void)
1155 mw.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1156 gtk_window_set_default_size(GTK_WINDOW(mw.win), 1200, 900);
1157 g_signal_connect(G_OBJECT(mw.win), "destroy", gtk_main_quit, NULL);
1159 gchar *priv = (cfg.private) ? "-private" : "";
1160 gchar *title = malloc(strlen(priv) + 7);
1161 if (!title) allocfail();
1162 sprintf(title, "%s%s", "chorizo", priv);
1163 gtk_window_set_title(GTK_WINDOW(mw.win), title);
1166 mw.notebook = gtk_notebook_new();
1167 gtk_notebook_set_scrollable(GTK_NOTEBOOK(mw.notebook), TRUE);
1168 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(mw.notebook), GTK_POS_LEFT);
1169 gtk_container_add(GTK_CONTAINER(mw.win), mw.notebook);
1170 g_signal_connect(G_OBJECT(mw.notebook), "switch-page",
1171 G_CALLBACK(notebook_switch_page), NULL);
1173 GtkCssProvider *css = gtk_css_provider_new();
1174 const char *css_data = "notebook header.left * { \
1177 padding-bottom: 0; \
1179 gtk_css_provider_load_from_data(css, css_data, strlen(css_data), NULL);
1180 gtk_style_context_add_provider_for_screen(
1181 gdk_screen_get_default(), GTK_STYLE_PROVIDER(css),
1182 GTK_STYLE_PROVIDER_PRIORITY_USER);
1184 gtk_widget_show_all(mw.win);
1188 mainwindow_title(gint idx)
1190 GtkWidget *child, *widg, *tablabel;
1192 child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx);
1193 if (child == NULL) return;
1195 widg = gtk_notebook_get_tab_label(GTK_NOTEBOOK(mw.notebook), child);
1196 tablabel = (GtkWidget *)g_object_get_data(G_OBJECT(widg),
1197 "chorizo-tab-label");
1198 text = gtk_label_get_text(GTK_LABEL(tablabel));
1199 gtk_window_set_title(GTK_WINDOW(mw.win), text);
1203 notebook_switch_page(GtkNotebook *nb, GtkWidget *p, guint idx, gpointer data)
1205 mainwindow_title(idx);
1209 quit_if_nothing_active(void)
1212 if (downloads == 0) {
1216 downloadmanager_show();
1222 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
1225 g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
1228 client_new(uri, NULL, TRUE, TRUE);
1235 show_web_view(WebKitWebView *web_view, gpointer data)
1237 struct Client *c = (struct Client *)data;
1239 gint idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
1241 gtk_widget_show_all(GTK_WIDGET(
1242 gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx)));
1243 gtk_widget_hide(c->isearch_box);
1245 if (c->focus_new_tab) {
1247 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook),
1250 gtk_widget_grab_focus(c->web_view);
1255 trust_user_certs(WebKitWebContext *wc)
1257 GTlsCertificate *cert;
1258 gchar *basedir, *absfile;
1261 basedir = g_build_filename(g_get_user_data_dir(), "chorizo", "certs",
1263 dir = g_dir_open(basedir, 0, NULL);
1266 file = g_dir_read_name(dir);
1267 while (file != NULL) {
1268 absfile = g_build_filename(g_get_user_data_dir(),
1269 "chorizo", "certs", file,
1271 cert = g_tls_certificate_new_from_file(absfile, NULL);
1275 "chorizo: warning: could not load trusted cert: %s\n",
1278 webkit_web_context_allow_tls_certificate_for_host(
1280 file = g_dir_read_name(dir);
1289 printf("%s %s\n", "chorizo", VERSION);
1293 main(int argc, char **argv)
1298 cfg.noncooperative_instances = FALSE;
1299 cfg.cooperative_alone = TRUE;
1300 closed_tabs = malloc(0);
1301 if (!closed_tabs) allocfail();
1303 while ((opt = getopt(argc, argv, "cpvV")) != -1) {
1306 cfg.noncooperative_instances = TRUE;
1319 "usage: chorizo [OPTION]... [URI]...\n");
1324 if (cfg.verbose) version();
1326 gtk_init(&argc, &argv);
1328 // Keep clipboard contents after program closes
1329 gtk_clipboard_store(gtk_clipboard_get_for_display(
1330 gdk_display_get_default(), GDK_SELECTION_CLIPBOARD));
1332 if (!cfg.noncooperative_instances) cooperation_setup();
1334 if (cfg.noncooperative_instances || cfg.cooperative_alone)
1335 init_default_web_context();
1337 downloadmanager_setup();
1340 if (optind >= argc) {
1341 client_new(cfg_home_uri, NULL, TRUE, TRUE);
1343 for (i = optind; i < argc; i++)
1344 client_new(argv[i], NULL, TRUE, TRUE);
1347 if (cfg.noncooperative_instances || cfg.cooperative_alone) {