browser.c
1/**
2 * Copyright (C) 2023 Chris Noxz
3 * Author(s): Chris Noxz <chris@noxz.tech>
4 *
5 * This program is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation, either version 3 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#include "browser.h"
20#include "config.h"
21
22
23void
24about_scheme_request(
25 WebKitURISchemeRequest *request,
26 gpointer data)
27{
28 (void) data;
29
30 GError *e; /* error holder */
31 GInputStream *s; /* stream to send */
32 GString *m; /* message of stream */
33 const gchar *p; /* path holder eg. 'config' */
34 gchar **w; /* temporary word holder */
35 gsize sl; /* size of stream */
36
37 p = request_path(request);
38
39 /* about:config */
40 if (!strcmp(p, "config")) {
41 g_string_append_printf((m = g_string_new(NULL)), "<html><body>"
42 "<table style=\"width:100%%;\">"
43 "<tr>"
44 "<th align=\"left\">Nonconfigurable Name</th>"
45 "<th align=\"left\">Value</th>"
46 "</tr>"
47 "<tr><td>"__NAME__" version</td><td>%s</td></tr>"
48 "<tr><td>Gtk version</td><td>%u.%u.%u</td></tr>"
49 "<tr><td>WebKit version</td><td>%u.%u.%u</td></tr>"
50 "<tr>"
51 "<th align=\"left\">Configurable Name</th>"
52 "<th align=\"left\">Value</th>"
53 "</tr>",
54 VERSION,
55 gtk_get_major_version(),
56 gtk_get_minor_version(),
57 gtk_get_micro_version(),
58 webkit_get_major_version(),
59 webkit_get_minor_version(),
60 webkit_get_micro_version());
61 for (int i = 0; i < CFN_LAST; i++) {
62 if (!cfg[i].e)
63 continue;
64 g_string_append_printf(m, "<tr><td>%s</td><td>", cfg[i].e);
65 switch (cfg[i].t) {
66 case CFG_STRING:
67 g_string_append_printf(m, "%s", cfg[i].v.s);
68 break;
69 case CFG_INT:
70 g_string_append_printf(m, "%d", cfg[i].v.i);
71 break;
72 case CFG_FLOAT:
73 g_string_append_printf(m, "%f", cfg[i].v.f);
74 break;
75 case CFG_BOOL:
76 g_string_append_printf(m, "%s", cfg[i].v.b ? "TRUE" : "FALSE");
77 break;
78 case CFG_LIST:
79 for (w = cfg[i].v.l; w && *w; w++)
80 g_string_append_printf(m, "%s%s", w == cfg[i].v.l
81 ? "" : "; ", *w);
82 break;
83 }
84 g_string_append_printf(m, "</td></tr>");
85 }
86 g_string_append_printf(m, "</table>");
87 g_string_append_printf(m, "</body></html>");
88 } else {
89 webkit_uri_scheme_request_finish_error(request, (e = g_error_new(
90 BROWSER_ERROR,
91 BER_INVALID_ABOUT_PATH,
92 "Invalid 'about:%s' page", p)));
93 g_error_free(e);
94 return;
95 }
96
97 sl = strlen(m->str);
98 s = g_memory_input_stream_new_from_data(m->str, sl, NULL);
99 webkit_uri_scheme_request_finish(request, s, sl, "text/html");
100 g_object_unref(s);
101 g_string_free(m, TRUE);
102}
103
104void
105attach_to_host(
106 GtkWidget *self)
107{
108 guchar *d = NULL; /* data holder */
109 gint l, /* data length */
110 f; /* data format */
111 GdkAtom t; /* data atom type */
112 GtkWidget *w; /* top widget (window?) */
113
114 if (GTK_IS_PLUG(w = gtk_widget_get_toplevel(self)) || !GTK_IS_WINDOW(w))
115 return;
116
117 if (gdk_property_get(get_widget_win(GTK_WIDGET(w)),
118 gdk_atom_intern(CFG_S(CFN_TAB_HOST), FALSE), GDK_NONE,
119 0, G_MAXLONG, FALSE, &t, &f, &l, &d)
120 && t == gdk_atom_intern("WINDOW", FALSE)
121 && f == 32
122 && l == sizeof(Window))
123 gl.embed = (long)*((Window*)d);
124 else
125 gl.embed = 0;
126 g_free(d);
127}
128
129void
130attach_to_window(
131 GtkWidget *self)
132{
133 GtkWidget *vbx;
134 GtkWidget *wv;
135
136 gl.embed = 0;
137
138 if (!gl.initialized)
139 return;
140
141 gl.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
142
143 if (!(vbx = gtk_bin_get_child(GTK_BIN(self))))
144 return;
145
146 if (!(wv = gtk_container_get_children(GTK_CONTAINER(vbx))->data))
147 return;
148
149 /* window settings */
150 gtk_window_set_default_size(GTK_WINDOW(gl.win), 800, 600);
151 gtk_window_set_title(GTK_WINDOW(gl.win), __NAME__);
152
153 /* connect signal callbacks */
154 CB(gl.win, "destroy", cb_quit);
155 CB(gl.win, "map-event", cb_map);
156
157 g_signal_handlers_disconnect_by_data(G_OBJECT(self), NULL);
158
159 g_object_ref(vbx);
160 gtk_container_remove(GTK_CONTAINER(self), vbx);
161 gtk_container_add(GTK_CONTAINER(gl.win), vbx);
162 g_object_unref(vbx);
163
164 gtk_widget_destroy(self);
165 gtk_widget_show_all(gl.win);
166 gtk_window_set_focus(GTK_WINDOW(gl.win), wv);
167}
168
169void
170fork_url(
171 const gchar *uri)
172{
173 char e[64];
174
175 if (gl.embed > 0 && snprintf(e, sizeof(e), "%lu", gl.embed) > 0)
176 execvp(gl.arg0, (char *[]){ gl.arg0, "-E", e, (char*)uri, NULL });
177 else
178 execvp(gl.arg0, (char *[]){ gl.arg0, (char*)uri, NULL });
179}
180
181void
182initialize(
183 const gchar *uri)
184{
185 gchar *u = NULL; /* translated uri */
186 int i;
187
188 /* communicate uri through ipc if constructed */
189 if (gl.initialized && uri) {
190 if ((u = resolve_uri(uri)) && fork() == 0)
191 fork_url(u);
192 g_free(u);
193 return; /* the request was handled elsewhere */
194 }
195
196 if (gl.initialized)
197 return;
198
199 /* do not create a new client if existing uri was resolved to NULL, as it
200 * was handled as an XDG-OPEN event. Still process uri == NULL for "create"
201 * callback signals, such as "Open Link in New Window". */
202 if (uri && !(u = resolve_uri(uri)))
203 return;
204
205 gl.wv = webkit_web_view_new();
206
207 /* connect signal callbacks */
208 CB(gl.wv, "button-release-event", cb_wv_hid);
209 CB(gl.wv, "close", cb_wv_close);
210 CB(gl.wv, "context-menu", cb_context_menu);
211 CB(gl.wv, "create", cb_wv_create);
212 CB(gl.wv, "decide-policy", cb_wv_decide_policy);
213 CB(gl.wv, "key-press-event", cb_wv_hid);
214 CB(gl.wv, "load-changed", cb_wv_load_changed);
215 CB(gl.wv, "load-failed-with-tls-errors", cb_wv_tls_load_failed);
216 CB(gl.wv, "mouse-target-changed", cb_wv_hover);
217 CB(gl.wv, "notify::estimated-load-progress",cb_wv_load_progress_changed);
218 CB(gl.wv, "notify::favicon", cb_favicon_changed);
219 CB(gl.wv, "notify::title", cb_title_changed);
220 CB(gl.wv, "notify::uri", cb_uri_changed);
221 CB(gl.wv, "scroll-event", cb_wv_hid);
222 CB(gl.wv, "web-process-crashed", cb_wv_crashed);
223 CB(gl.wv, "enter-fullscreen", cb_wv_fullscreen_enter);
224 CB(gl.wv, "leave-fullscreen", cb_wv_fullscreen_leave);
225
226 /* load config into webkit settings */
227 gl.settings = webkit_settings_new();
228 for (i = 0; i < CFN_LAST; i++)
229 if (cfg[i].s)
230 g_object_set(
231 G_OBJECT(gl.settings),
232 cfg[i].s,
233 cfg[i].i ? (Arg)!(cfg[i].v.b) : (cfg[i].v),
234 NULL
235 );
236 webkit_web_view_set_settings(WEBKIT_WEB_VIEW(gl.wv), gl.settings);
237
238 if (CFG_L(CFN_ACCEPTED_LANGUAGES))
239 webkit_web_context_set_preferred_languages(
240 webkit_web_view_get_context(WEBKIT_WEB_VIEW(gl.wv)),
241 CFG_L(CFN_ACCEPTED_LANGUAGES)
242 );
243 if (CFG_S(CFN_COOKIE_FILE))
244 webkit_cookie_manager_set_persistent_storage(
245 webkit_web_context_get_cookie_manager(
246 webkit_web_view_get_context(WEBKIT_WEB_VIEW(gl.wv))
247 ),
248 CFG_S(CFN_COOKIE_FILE),
249 WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT
250 );
251 if (CFG_S(CFN_PROXY_URI))
252 webkit_website_data_manager_set_network_proxy_settings(
253 webkit_web_view_get_website_data_manager(WEBKIT_WEB_VIEW(gl.wv)),
254 WEBKIT_NETWORK_PROXY_MODE_CUSTOM,
255 webkit_network_proxy_settings_new(
256 CFG_S(CFN_PROXY_URI), CFG_L(CFN_PROXY_IGNORE)
257 )
258 );
259 webkit_web_view_set_zoom_level(
260 WEBKIT_WEB_VIEW(gl.wv), CFG_F(CFN_ZOOM_LEVEL)
261 );
262
263 /* create entry */
264 gl.entry = gtk_entry_new();
265 CB(gl.entry, "key-press-event", cb_entry_hid);
266 CB(gl.entry, "icon-release", cb_entry_icon_hid);
267 gtk_entry_set_icon_from_icon_name(
268 GTK_ENTRY(gl.entry), GTK_ENTRY_ICON_SECONDARY,
269 CFG_B(CFN_DISABLE_JAVASCRIPT) ? ICON_JS_OFF : ICON_JS_ON
270 );
271
272 /* create vertical box to store the web view and the entry */
273 gl.vbx = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
274 gtk_box_pack_start(GTK_BOX(gl.vbx), gl.wv, TRUE, TRUE, 0);
275 gtk_box_pack_start(GTK_BOX(gl.vbx), gl.entry, FALSE, FALSE, 0);
276 gtk_container_set_focus_child(GTK_CONTAINER(gl.vbx), gl.wv);
277
278 /* pack everything in a vertical box */
279 gtk_container_add(GTK_CONTAINER(gl.win), gl.vbx);
280
281 /* manage which tab should be focused */
282 gtk_widget_show_all(gl.win);
283 gtk_window_set_focus(GTK_WINDOW(gl.win), gl.wv);
284
285 /* finally load the uri */
286 if (u)
287 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(gl.wv), u);
288 g_free(u);
289
290 load_user_styles(WEBKIT_WEB_VIEW(gl.wv));
291 load_user_scripts(WEBKIT_WEB_VIEW(gl.wv));
292
293 if (uri && !strcmp(CFG_S(CFN_HOME_URI), uri))
294 gtk_widget_grab_focus(gl.entry);
295
296 gl.initialized = TRUE;
297}
298
299void
300terminate(void)
301{
302 /* disconnect all handlers */
303 g_signal_handlers_disconnect_by_data(G_OBJECT(gl.wv), NULL);
304
305 gl.initialized = FALSE;
306 quit();
307}
308
309gboolean
310command(
311 const gchar *t)
312{
313 if (t[0] == '/') { /* in-page search */
314 g_free(gl.search_text);
315 gl.search_text = g_strdup(t + 1);
316 search(IPS_INIT);
317 return TRUE;
318 } else if (t[0] == 'q') { /* quit (vim-like) */
319 terminate();
320 return TRUE;
321 }
322
323 return FALSE;
324}
325
326void
327create_context_menu(
328 WebKitContextMenu *context_menu,
329 WebKitHitTestResult *hit_test_result)
330{
331 guint x; /* hit test result context */
332
333 x = webkit_hit_test_result_get_context(hit_test_result);
334
335 webkit_context_menu_prepend(
336 context_menu, webkit_context_menu_item_new_separator()
337 );
338
339 /* if document is the only context */
340 if (x == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)
341 return create_context_menu_item(
342 context_menu,
343 "open-external",
344 "Open Page Externally",
345 cb_open_external
346 ); /* no need to check further */
347
348 if (x & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
349 create_context_menu_item(
350 context_menu,
351 "open-external",
352 "Open Link Externally",
353 cb_open_external
354 );
355
356 /* requires javascript for DOM access from here on */
357 if (!webkit_settings_get_enable_javascript(gl.settings))
358 return;
359
360 if (x & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
361 create_context_menu_item(
362 context_menu,
363 "selection-search",
364 "Search Selection",
365 cb_selection_search
366 );
367}
368
369void
370create_context_menu_item(
371 WebKitContextMenu *context_menu,
372 const gchar *name,
373 const gchar *label,
374 void *action)
375{
376 GAction *a = (GAction*)g_simple_action_new(name, NULL);
377
378 CB(a, "activate", action);
379 webkit_context_menu_prepend(
380 context_menu,
381 webkit_context_menu_item_new_from_gaction(a, label, NULL)
382 );
383 g_object_unref(a);
384}
385
386void
387download_blob(
388 WebKitDownload *d)
389{
390 WebKitURIResponse *r = webkit_download_get_response(d);
391 const gchar *u = webkit_uri_response_get_uri(r);
392
393 if (strncmp(u, "blob:", 5) && strncmp(u, "data:", 5))
394 return;
395
396 webkit_download_set_destination(d, CFG_S(CFN_BLOB_FILE));
397}
398
399void
400download_response(
401 WebKitURIResponse *r)
402{
403 xdg_open("adjidl:", webkit_uri_response_get_uri(r), TRUE);
404}
405
406gchar*
407get_uri(
408 GtkWidget *wv)
409{
410 const gchar *u;
411
412 if (!(u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(wv))))
413 return NULL;
414
415 if (!strncmp(u, ABOUT_SCHEME":", strlen(ABOUT_SCHEME":")))
416 return g_strconcat("about:", u + strlen(ABOUT_SCHEME":"), NULL);
417
418 return g_strdup(u);
419}
420
421gboolean
422handle_fullscreen(
423 gboolean fullscreen)
424{
425 GdkDisplay *d = gdk_display_get_default();
426 GdkWindow *p = GTK_IS_PLUG(gl.win)
427 ? get_socket_win(gl.win)
428 : to_gdk_win(d, gl.embed);
429
430 if (fullscreen) /* hide or show widgets depending on fullscreen */
431 gtk_widget_hide(gl.entry);
432 else
433 gtk_widget_show_all(gl.entry);
434
435 if (!p) /* if not embeded we are done! */
436 return TRUE;
437
438 gtk_widget_grab_focus(gl.wv);
439
440 if (fullscreen) /* tell parent to go fullscreen */
441 gdk_window_fullscreen(p);
442 else
443 gdk_window_unfullscreen(p);
444 return TRUE;
445}
446
447void
448handle_popup(
449 const gchar *uri)
450{
451 char u[8192];
452 snprintf(u, sizeof(u), "%lu:%s", gl.embed, uri);
453
454 switch (CFG_I(CFN_POPUP_POLICY)) {
455 case PUP_ALLOW:
456 initialize(uri);
457 break;
458 case PUP_EXTERNAL:
459 xdg_open("adjipu:", u, TRUE);
460 break;
461 }
462}
463
464gboolean
465key_common(
466 GdkEventKey *event)
467{
468 gchar *u = NULL;
469 gboolean f = is_fullscreen();
470
471 /* key presses not using the alt key */
472 switch (event->keyval) {
473 case GDK_KEY_F11: /* toggle window fullscreen state */
474 if (f) {
475 gtk_window_unfullscreen(GTK_WINDOW(gl.win));
476 handle_fullscreen(!f);
477 } else {
478 gtk_window_fullscreen(GTK_WINDOW(gl.win));
479 handle_fullscreen(f);
480 }
481 return TRUE;
482 }
483
484 if (event->state & GDK_CONTROL_MASK) /* ctrl key commands */
485 switch (event->keyval) {
486 case GDK_KEY_h:
487 case GDK_KEY_j:
488 case GDK_KEY_k:
489 case GDK_KEY_l: /* in-page movement by emulating arrow keys... */
490 case GDK_KEY_g:
491 case GDK_KEY_G: /* ... as well as home and end */
492 event->keyval /* translate key press accordingly */
493 = event->keyval == GDK_KEY_h ? GDK_KEY_Left
494 : event->keyval == GDK_KEY_j ? GDK_KEY_Down
495 : event->keyval == GDK_KEY_k ? GDK_KEY_Up
496 : event->keyval == GDK_KEY_l ? GDK_KEY_Right
497 : event->keyval == GDK_KEY_g ? GDK_KEY_Home
498 : /* */ GDK_KEY_End;
499 event->state = 0;
500 gtk_propagate_event(GTK_WIDGET(gl.wv), (GdkEvent*)event);
501 return TRUE;
502 }
503
504 if (f) /* do not handle key events in fullscreen */
505 return FALSE;
506
507 /* only handle key presses using the alt key */
508 if (!(event->state & GDK_MOD1_MASK))
509 return FALSE;
510
511 switch (event->keyval) {
512 case GDK_KEY_q: /* destroy client and close tab */
513 terminate();
514 return TRUE;
515 case GDK_KEY_w: /* go to home page */
516 if ((u = resolve_uri(CFG_S(CFN_HOME_URI))))
517 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(gl.wv), u);
518 g_free(u);
519 return TRUE;
520 case GDK_KEY_r: /* reload, while bypassing cache */
521 webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(gl.wv));
522 return TRUE;
523 case GDK_KEY_i:
524 toggle_inspector();
525 return TRUE;
526 case GDK_KEY_o: /* focus entry */
527 gtk_widget_grab_focus(gl.entry);
528 return TRUE;
529 case GDK_KEY_n: /* search forward */
530 search(IPS_FORWARD);
531 return TRUE;
532 case GDK_KEY_N: /* search backward */
533 search(IPS_BACKWARD);
534 return TRUE;
535 case GDK_KEY_slash: /* initiate in-page search */
536 gtk_widget_grab_focus(gl.entry);
537 gtk_entry_set_text(GTK_ENTRY(gl.entry), ":/");
538 gtk_editable_set_position(GTK_EDITABLE(gl.entry), -1);
539 return TRUE;
540 case GDK_KEY_0:
541 case GDK_KEY_minus:
542 case GDK_KEY_equal: /* set zoom level */
543 webkit_web_view_set_zoom_level(
544 WEBKIT_WEB_VIEW(gl.wv),
545 event->keyval == GDK_KEY_0
546 ? CFG_F(CFN_ZOOM_LEVEL)
547 : webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(gl.wv))
548 + ((event->keyval == GDK_KEY_minus) ? -0.1 : 0.1)
549 );
550 return TRUE;
551 case GDK_KEY_H: /* go back in history */
552 webkit_web_view_go_back(WEBKIT_WEB_VIEW(gl.wv));
553 return TRUE;
554 case GDK_KEY_L: /* go forward in history */
555 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(gl.wv));
556 return TRUE;
557 case GDK_KEY_s: /* toggle javascript (on or off) */
558 set_javascript_policy(JSP_TOGGLE);
559 return TRUE;
560 case GDK_KEY_c: /* toggle tls error policy (ignore or fail) */
561 toggle_tls_error_policy();
562 return TRUE;
563 case GDK_KEY_p: /* open print dialog */
564 webkit_print_operation_run_dialog(
565 webkit_print_operation_new(WEBKIT_WEB_VIEW(gl.wv)),
566 GTK_WINDOW(gl.win)
567 );
568 return TRUE;
569 }
570
571 /* check external handler keys */
572 if (cfg[CFN_EXTERNAL_HANDLER_KEYS].v.b &&
573 event->keyval < 256 &&
574 g_strv_contains(
575 CFG_L(CFN_EXTERNAL_HANDLER_KEYS), (gchar[]){(gchar)event->keyval, 0}
576 )) {
577 open_external((gchar)event->keyval);
578 return TRUE;
579 }
580
581 return FALSE;
582}
583
584gboolean
585key_entry(
586 GdkEventKey *event)
587{
588 const gchar *t;
589 gchar *u = NULL,
590 *l = NULL;
591 int p,
592 m;
593
594 /* handle key presses using the alt key (takes precedence over common) */
595 if (event->state & GDK_MOD1_MASK) {
596 switch (event->keyval) { /* movements inside entry */
597 case GDK_KEY_l:
598 case GDK_KEY_h:
599 p = gtk_editable_get_position(GTK_EDITABLE(gl.entry));
600 m = gtk_entry_get_text_length(GTK_ENTRY(gl.entry));
601 p += event->keyval == GDK_KEY_l
602 ? m > p ? 1 : 0 /* move right */
603 : 0 < p ? -1 : 0; /* move left */
604 gtk_editable_set_position(GTK_EDITABLE(gl.entry), p);
605 return TRUE;
606 }
607 }
608
609 /* handle common key presses */
610 if (key_common(event))
611 return TRUE;
612
613 /* handle any key press (not just using the alt key) */
614 switch (event->keyval) {
615 case GDK_KEY_KP_Enter:
616 case GDK_KEY_Return:
617 gtk_widget_grab_focus(gl.wv);
618 if (!(t = gtk_entry_get_text(GTK_ENTRY(gl.entry))))
619 return TRUE;
620 if (t[0] != ':') { /* if not a command */
621 /* store current uri before loading new uri */
622 l = get_uri(gl.wv);
623 if ((u = resolve_uri(t)))
624 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(gl.wv), u);
625 g_free(u);
626 /* fix: notify::uri won't be raised if current uri is unchanged */
627 if (!strcmp(l, (u = get_uri(gl.wv))))
628 uri_changed();
629 g_free_all((gpointer[]){u, l, NULL});
630 return TRUE;
631 } else if (command(t + 1)) { /* if command */
632 return TRUE;
633 } /* FALL THROUGH */
634 case GDK_KEY_Escape:
635 gtk_widget_grab_focus(gl.wv);
636 u = get_uri(gl.wv);
637 gtk_entry_set_text(GTK_ENTRY(gl.entry), (!u ? __NAME__ : u));
638 gtk_editable_set_position(GTK_EDITABLE(gl.entry), -1);
639 g_free(u);
640 return TRUE;
641 }
642
643 return FALSE;
644}
645
646gboolean
647key_web_view(
648 GdkEvent *event)
649{
650 gdouble dx, /* scroll x-delta */
651 dy; /* scroll y-delta */
652
653 /* handle common key presses */
654 if (event->type == GDK_KEY_PRESS && key_common((GdkEventKey *)event))
655 return TRUE;
656
657 /* escape key: stop web rendering */
658 if (event->type == GDK_KEY_PRESS
659 && ((GdkEventKey *)event)->keyval == GDK_KEY_Escape) {
660 webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(gl.wv));
661 gtk_entry_set_progress_fraction(GTK_ENTRY(gl.entry), 0);
662 /* mouse button events */
663 } else if (event->type == GDK_BUTTON_RELEASE) {
664 switch (((GdkEventButton *)event)->button) {
665 case 2: /* mouse middle button: create new client */
666 if (gl.hover_uri) {
667 initialize(gl.hover_uri);
668 return TRUE;
669 }
670 break;
671 case 8: /* mouse button back: go back in history */
672 webkit_web_view_go_back(WEBKIT_WEB_VIEW(gl.wv));
673 return TRUE;
674 case 9: /* mouse button forward: go forward in history */
675 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(gl.wv));
676 return TRUE;
677 }
678 /* scroll using alt pressed: zoom in or out */
679 } else if (event->type == GDK_SCROLL
680 && (((GdkEventScroll *)event)->state & GDK_MOD1_MASK)) {
681 gdk_event_get_scroll_deltas(event, &dx, &dy);
682 webkit_web_view_set_zoom_level(
683 WEBKIT_WEB_VIEW(gl.wv),
684 dx != 0
685 ? CFG_F(CFN_ZOOM_LEVEL)
686 : webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(gl.wv)) + -dy * 0.1
687 );
688 return TRUE;
689 }
690
691 return FALSE;
692}
693
694void
695load_changed(
696 WebKitLoadEvent event_type)
697{
698 switch (event_type) {
699 /* when page load starts, reset everything */
700 case WEBKIT_LOAD_STARTED:
701 gl.https = FALSE;
702 if (gl.error_page)
703 gl.error_page = FALSE;
704 else
705 g_clear_object(&gl.failed_crt);
706 break;
707 /* when page load is redirected, continue as usual */
708 case WEBKIT_LOAD_REDIRECTED:
709 break;
710 /* when page load is committed, get https and tls state */
711 case WEBKIT_LOAD_COMMITTED:
712 gl.https = webkit_web_view_get_tls_info(
713 WEBKIT_WEB_VIEW(gl.wv), &gl.crt, &gl.tls_error
714 );
715 break;
716 /* when page load is finished, run all user scripts */
717 case WEBKIT_LOAD_FINISHED:
718 load_user_styles(WEBKIT_WEB_VIEW(gl.wv));
719 load_user_scripts(WEBKIT_WEB_VIEW(gl.wv));
720 break;
721 }
722
723 update_title();
724}
725
726void
727load_configuration(void)
728{
729 const gchar *e; /* environment variable name */
730 int i;
731
732 /* set global defaults */
733 gl.search_text = NULL; /* in page search phrase */
734 gl.state_lock = TRUE; /* prevents state save */
735 gl.initialized = FALSE; /* is client initialized? */
736
737 /* load default configuration */
738 for (i = 0; i < CFN_LAST; i++) {
739 if (cfg[i].e && (e = g_getenv(cfg[i].e))) {
740 switch (cfg[i].t) {
741 case CFG_INT: cfg[i].v.i = atoi(e); break;
742 case CFG_FLOAT: cfg[i].v.f = atof(e); break;
743 case CFG_BOOL: cfg[i].v.b = TRUE; break;
744 case CFG_STRING: cfg[i].v.s = g_strdup(e); break;
745 case CFG_LIST: cfg[i].v.l = g_strsplit(e, ",", -1); break;
746 }
747 }
748 }
749}
750
751void
752load_stdin(void)
753{
754 int c; /* character holder */
755 GString *s = g_string_new(NULL);
756
757 /* read until end of stream */
758 while ((c = getc(stdin)) != EOF)
759 g_string_append_c(s, (gchar)c);
760
761 /* load the html content, which won't survive a reload */
762 initialize(NULL);
763 webkit_web_view_load_html(WEBKIT_WEB_VIEW(gl.wv), s->str, NULL);
764 g_string_free(s, TRUE);
765}
766
767void
768load_user_scripts(
769 WebKitWebView *web_view)
770{
771 gchar *c = NULL, /* file content */
772 *p = NULL, /* path (file) */
773 *b = NULL; /* base directory (scripts) */
774 const gchar *e = NULL; /* directory file entry */
775 GDir *s = NULL; /* user script directory */
776
777 webkit_user_content_manager_remove_all_scripts(
778 webkit_web_view_get_user_content_manager(web_view)
779 );
780
781 IUS(web_view, SCRIPT_ALL_FRAMES, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES);
782 IUS(web_view, SCRIPT_MAIN_FRAME, WEBKIT_USER_CONTENT_INJECT_TOP_FRAME);
783
784 b = g_build_filename(CFG_DIR, __NAME__, CFG_S(CFN_USER_SCRIPT_DIR), NULL);
785 if (!(s = g_dir_open(b, 0, NULL)))
786 return g_free(b);
787
788 while ((e = g_dir_read_name(s))) {
789 if (g_str_has_suffix((p = g_build_filename(b, e, NULL)), ".js")
790 && g_file_get_contents(p, &c, NULL, NULL)) {
791 IUS(web_view, c, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES);
792 g_free(c);
793 }
794 g_free(p);
795 }
796
797 g_dir_close(s);
798 g_free(b);
799}
800
801void
802load_user_styles(
803 WebKitWebView *web_view)
804{
805 gchar *c = NULL, /* file content */
806 *p = NULL, /* path (file) */
807 *b = NULL; /* base directory (scripts) */
808 const gchar *e = NULL, /* directory file entry */
809 *u = NULL; /* uri holder */
810 GDir *s = NULL; /* user style directory */
811 gchar **m = NULL; /* match container */
812
813 b = g_build_filename(CFG_DIR, __NAME__, CFG_S(CFN_USER_CSS_DIR), NULL);
814 if (!(s = g_dir_open(b, 0, NULL)))
815 return g_free(b);
816
817 if (!(u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view))))
818 return; /* end load if no uri exists */
819
820 webkit_user_content_manager_remove_all_style_sheets(
821 webkit_web_view_get_user_content_manager(web_view)
822 );
823
824 while ((e = g_dir_read_name(s))) {
825 if (g_str_has_suffix((p = g_build_filename(b, e, NULL)), ".css")
826 && g_file_get_contents(p, &c, NULL, NULL)) {
827 /* reject uri specific css that do not match */
828 m = g_regex_split_simple("/\\* URL -- (.*) \\*/", c, 0, 0);
829 if (!m[1] || (m[1] != NULL && !strncmp(m[1], u, strlen(m[1]))))
830 IUSS(web_view, c, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES);
831 g_free(m);
832 g_free(c);
833 }
834 g_free(p);
835 }
836
837 g_dir_close(s);
838 g_free(b);
839}
840
841void
842main_window_setup(void)
843{
844 /* define window elements */
845 gl.win = gl.embed > 0
846 ? gtk_plug_new(gl.embed)
847 : gtk_window_new(GTK_WINDOW_TOPLEVEL);
848
849 /* window settings */
850 gtk_window_set_default_size(GTK_WINDOW(gl.win), 800, 600);
851 gtk_window_set_title(GTK_WINDOW(gl.win), __NAME__);
852
853 /* connect signal callbacks */
854 CB(gl.win, "destroy", cb_quit);
855
856 if (gl.embed)
857 CB(gl.win, "unmap-event", cb_unmap);
858 CB(gl.win, "map-event", cb_map);
859}
860
861void
862open_external(
863 const gchar s)
864{
865 char b[URI_MAX]; /* command line buffer */
866 gchar *p = NULL; /* external handler path */
867 p = g_build_filename(
868 CFG_DIR, __NAME__, CFG_S(CFN_EXTERNAL_HANDLER_FILE), NULL
869 );
870 if (s)
871 sprintf(b, "%s %c:%s &", p, s, gtk_entry_get_text(GTK_ENTRY(gl.entry)));
872 else
873 sprintf(b, "%s %s &", p, gtk_entry_get_text(GTK_ENTRY(gl.entry)));
874 g_free(p);
875
876 system(b);
877}
878
879void
880quit(void)
881{
882 gtk_main_quit();
883}
884
885void
886render_tls_error(
887 gchar *uri,
888 GTlsCertificate *crt,
889 GTlsCertificateFlags crt_flags)
890{
891 GString *m = NULL; /* message (error) */
892 gchar *h = NULL, /* html code */
893 *s = NULL, /* subject name */
894 *i = NULL, /* issuer name */
895 *b = NULL, /* not before date */
896 *a = NULL, /* not after date */
897 *p = NULL; /* pem block */
898 GDateTime *bd = NULL, /* not before date */
899 *ad = NULL; /* not after date */
900
901 m = g_string_new(NULL);
902 gl.failed_crt = g_object_ref(crt);
903 gl.tls_error = crt_flags;
904 gl.error_page = TRUE;
905
906 /* translate all flags to messages */
907 if (gl.tls_error & G_TLS_CERTIFICATE_UNKNOWN_CA)
908 g_string_append(m, MSG_TLS_CERTIFICATE_UNKNOWN_CA);
909 if (gl.tls_error & G_TLS_CERTIFICATE_BAD_IDENTITY)
910 g_string_append(m, MSG_TLS_CERTIFICATE_BAD_IDENTITY);
911 if (gl.tls_error & G_TLS_CERTIFICATE_NOT_ACTIVATED)
912 g_string_append(m, MSG_TLS_CERTIFICATE_NOT_ACTIVATED);
913 if (gl.tls_error & G_TLS_CERTIFICATE_EXPIRED)
914 g_string_append(m, MSG_TLS_CERTIFICATE_EXPIRED);
915 if (gl.tls_error & G_TLS_CERTIFICATE_REVOKED)
916 g_string_append(m, MSG_TLS_CERTIFICATE_REVOKED);
917 if (gl.tls_error & G_TLS_CERTIFICATE_INSECURE)
918 g_string_append(m, MSG_TLS_CERTIFICATE_INSECURE);
919 if (gl.tls_error & G_TLS_CERTIFICATE_GENERIC_ERROR)
920 g_string_append(m, MSG_TLS_CERTIFICATE_GENERIC_ERROR);
921
922 /* construct html code and load it */
923 g_object_get(crt, "subject-name", &s, NULL);
924 g_object_get(crt, "issuer-name", &i, NULL);
925 g_object_get(crt, "not-valid-before", &bd, NULL);
926 g_object_get(crt, "not-valid-after", &ad, NULL);
927 g_object_get(crt, "certificate-pem", &p, NULL);
928 b = g_date_time_format_iso8601(bd);
929 a = g_date_time_format_iso8601(ad);
930 h = g_strdup_printf(TLS_MSG_FORMAT, uri, m->str, s, i, b, a, p);
931 webkit_web_view_load_alternate_html(WEBKIT_WEB_VIEW(gl.wv), h, uri, NULL);
932
933 g_string_free(m, TRUE);
934 g_free_all((gpointer[]){h, s, i, b, a, p, NULL});
935 g_date_time_unref(ad);
936 g_date_time_unref(bd);
937}
938
939void
940save_history(
941 const gchar *t)
942{
943 FILE *fp;
944
945 if (!CFG_S(CFN_HISTORY_FILE))
946 return;
947
948 if (!strcmp(t, ABOUT_BLANK))
949 return;
950
951 if (!(fp = fopen(CFG_S(CFN_HISTORY_FILE), "a")))
952 return perror(__NAME__": Error opening history file");
953
954 fprintf(fp, "%s\n", t);
955 fclose(fp);
956}
957
958void
959search(
960 enum inpage_search_type type)
961{
962 if (!gl.search_text)
963 return;
964
965 switch (type) {
966 case IPS_INIT:
967 webkit_find_controller_search(
968 webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(gl.wv)),
969 gl.search_text,
970 WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE
971 | WEBKIT_FIND_OPTIONS_WRAP_AROUND,
972 G_MAXUINT
973 );
974 break;
975 case IPS_FORWARD:
976 webkit_find_controller_search_next(
977 webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(gl.wv))
978 );
979 break;
980 case IPS_BACKWARD:
981 webkit_find_controller_search_previous(
982 webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(gl.wv))
983 );
984 break;
985 }
986}
987
988void
989selection_search(void)
990{
991 webkit_web_view_evaluate_javascript(
992 WEBKIT_WEB_VIEW(gl.wv),
993 gl.first_search ? SCRIPT_SEARCH : SCRIPT_SEARCH_EXTRA,
994 -1,
995 NULL,
996 NULL,
997 NULL,
998 cb_selection_search_finished,
999 NULL
1000 );
1001}
1002
1003void
1004selection_search_finished(
1005 GAsyncResult *result)
1006{
1007 JSCValue *v = NULL;
1008 gchar *s = NULL,
1009 *u = NULL;
1010
1011 if (!(v = webkit_web_view_evaluate_javascript_finish(
1012 WEBKIT_WEB_VIEW(gl.wv), result, NULL)))
1013 return;
1014
1015 if (jsc_value_is_string(v)
1016 && strlen(s = jsc_value_to_string(v))
1017 && !jsc_context_get_exception(jsc_value_get_context(v))
1018 && (u = resolve_uri(s)))
1019 initialize(u);
1020 else if (gl.first_search) {
1021 gl.first_search = FALSE;
1022 selection_search();
1023 }
1024
1025 g_free_all((gpointer[]){u, s, NULL});
1026}
1027
1028void
1029set_default_web_context(void)
1030{
1031 gchar *p = NULL; /* web extensions path */
1032 WebKitWebContext *c; /* web context */
1033
1034 c = webkit_web_context_get_default();
1035
1036 p = g_build_filename(CFG_DIR, __NAME__, CFG_S(CFN_WEB_EXTENSION_DIR), NULL);
1037#if GTK_CHECK_VERSION(3, 98, 0) /* seems to be fixed in 3.98.0, we'll see */
1038 webkit_web_context_set_sandbox_enabled(c, TRUE);
1039 webkit_web_context_add_path_to_sandbox(c, p, TRUE);
1040#else
1041 webkit_web_context_set_web_extensions_directory(c, p);
1042#endif
1043
1044 CB(c, "download-started", cb_download_start);
1045 webkit_web_context_set_favicon_database_directory(c, NULL);
1046 webkit_web_context_register_uri_scheme(
1047 c, ABOUT_SCHEME, (WebKitURISchemeRequestCallback)about_scheme_request,
1048 NULL, NULL
1049 );
1050
1051 g_free(p);
1052}
1053
1054void
1055set_hover_uri(
1056 WebKitHitTestResult *hit_test_result)
1057{
1058 const char *t; /* entry text holder */
1059 gchar *u = NULL; /* uri text holder */
1060
1061 g_free(gl.hover_uri);
1062
1063 /* only display hovered links */
1064 if (webkit_hit_test_result_context_is_link(hit_test_result)) {
1065 t = webkit_hit_test_result_get_link_uri(hit_test_result);
1066 gl.hover_uri = g_strdup(t);
1067 } else {
1068 u = get_uri(gl.wv);
1069 gl.hover_uri = NULL;
1070 }
1071
1072 if (!gtk_widget_is_focus(gl.entry))
1073 gtk_entry_set_text(GTK_ENTRY(gl.entry), u ? u : t);
1074
1075 g_free(u);
1076}
1077
1078void
1079set_javascript_policy(
1080 enum javascript_policy policy)
1081{
1082 webkit_settings_set_enable_javascript_markup(
1083 gl.settings,
1084 policy == JSP_TOGGLE
1085 ? !webkit_settings_get_enable_javascript_markup(gl.settings)
1086 : policy
1087 );
1088 gtk_entry_set_icon_from_icon_name(
1089 GTK_ENTRY(gl.entry),
1090 GTK_ENTRY_ICON_SECONDARY,
1091 webkit_settings_get_enable_javascript_markup(gl.settings)
1092 ? ICON_JS_ON
1093 : ICON_JS_OFF
1094 );
1095 webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(gl.wv));
1096}
1097
1098void
1099toggle_inspector(void)
1100{
1101 WebKitWebInspector *i;
1102
1103 i = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gl.wv));
1104
1105 /* assumes that the inspector has not been detached by the user */
1106 if (webkit_web_inspector_is_attached(i))
1107 webkit_web_inspector_close(i);
1108 else
1109 webkit_web_inspector_show(i);
1110}
1111
1112void
1113toggle_tls_error_policy(void)
1114{
1115 webkit_website_data_manager_set_tls_errors_policy(
1116 webkit_web_view_get_website_data_manager(WEBKIT_WEB_VIEW(gl.wv)),
1117 !webkit_website_data_manager_get_tls_errors_policy(
1118 webkit_web_view_get_website_data_manager(WEBKIT_WEB_VIEW(gl.wv))
1119 )
1120 );
1121 webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(gl.wv));
1122}
1123
1124gchar *
1125resolve_uri(
1126 const gchar *t)
1127{
1128 gchar *u = NULL, /* uri to return */
1129 *l = NULL, /* temporary string */
1130 *e = NULL; /* uri encoded string */
1131 const gchar *s = NULL;
1132 wordexp_t x; /* wordexp struct */
1133 int r = -1; /* result holder for wordexp */
1134
1135 /* cannot expand string in file scheme, so try without scheme */
1136 if (!strncmp("file://", t, 7) && (t[7] == '~' || t[7] == '$'))
1137 return resolve_uri(t + 7);
1138
1139 /* use internal about page so that about: prefix is ignored by WebKit */
1140 if (!strncmp(t, "about:", 6) && strcmp(t, ABOUT_BLANK))
1141 u = g_strdup_printf(ABOUT_SCHEME":%s", t + 6);
1142 /* check if valid scheme, and if so just create a copy of the text */
1143 else if ((s = g_uri_peek_scheme(t))
1144 && g_strv_contains(CFG_L(CFN_URI_SCHEMES), s))
1145 u = g_strdup(t);
1146 /* if no match, then test xdg schemes (schemes that are redirected) */
1147 else if (s && CFG_L(CFN_XDG_SCHEMES)
1148 && g_strv_contains(CFG_L(CFN_XDG_SCHEMES), s))
1149 xdg_open(s, t, FALSE);
1150 /* if path is local, use the file scheme, else try to see if the string is
1151 * expandable and is a local path */
1152 else if ((l = realpath(t, NULL)) || ((r = wordexp(t, &x, 0)) == 0
1153 && (l = resolve_uri_words(x.we_wordc, x.we_wordv))))
1154 u = g_strdup_printf("file://%s", l);
1155 /* else, check if the text can be interpreted as a valid https uri; it's
1156 * not enough to check if uri is valid - check for period and no spaces */
1157 else if (strchr(t, '.') && !strchr(t, ' ')
1158 && g_uri_is_valid((l = g_strdup_printf("https://%s", t)), 0, NULL))
1159 u = g_strdup(l);
1160 /* fallback to web search, using a specified search engine */
1161 else
1162 u = g_strdup_printf(
1163 CFG_S(CFN_SEARCH_ENGINE_URI_FORMAT),
1164 (e = g_uri_escape_string(t, NULL, FALSE))
1165 );
1166
1167 if (r == 0 || r == WRDE_NOSPACE) /* free on success (r == 0) and NOSPACE */
1168 wordfree(&x);
1169 g_free_all((gpointer[]){e, l, NULL});
1170 return u; /* return a pointer that the caller is responsible for freeing */
1171}
1172
1173char *
1174resolve_uri_words(
1175 int c,
1176 char **w)
1177{
1178 gchar *d = NULL, /* uri decoded string */
1179 *p = NULL, /* path to return */
1180 *s = NULL; /* concatenated string */
1181 size_t l, /* arbitrary length holder */
1182 wl[c]; /* word length */
1183 int i;
1184
1185 for (i = 0, l = 0; i < c; l += (wl[i] = strlen(w[i])) + 1, i++);
1186
1187 if (!(s = (char *)malloc(l * sizeof(char))))
1188 die(__NAME__ ": fatal: malloc failed\n");
1189
1190 /* concatenate, or join, words into a space separated string */
1191 for (i = 0, l = 0; i < c; l += wl[i], i++) {
1192 memcpy(s + l, w[i], wl[i]);
1193 memcpy(s + l++ + wl[i], i == c - 1 ? "\0" : " ", 1);
1194 }
1195
1196 p = realpath((d = g_uri_unescape_string(s, NULL)), NULL);
1197
1198 g_free_all((gpointer[]){d, s, NULL});
1199 return p; /* return a pointer that the caller is responsible for freeing */
1200}
1201
1202void
1203update_favicon(void)
1204{
1205 cairo_surface_t *f; /* favicon */
1206 GdkPixbuf *b, /* pix buffer */
1207 *s; /* pix buffer (scaled) */
1208 int d; /* scaled dimension */
1209
1210 /* set fallback favicon */
1211 gtk_window_set_icon_name(GTK_WINDOW(gl.win), ICON_GLOBE);
1212
1213 if (!(f = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(gl.wv))))
1214 return;
1215
1216 if (!(b = gdk_pixbuf_get_from_surface(f, 0, 0,
1217 cairo_image_surface_get_width(f),
1218 cairo_image_surface_get_height(f))))
1219 return;
1220
1221 /* setting the window icon enables other programs to retrieve the icon */
1222 d = 16 * gtk_widget_get_scale_factor(gl.win);
1223 s = gdk_pixbuf_scale_simple(b, d, d, GDK_INTERP_BILINEAR);
1224 gtk_window_set_icon(GTK_WINDOW(gl.win), s);
1225
1226 g_object_unref(s);
1227 g_object_unref(b);
1228}
1229
1230void
1231update_load_progress(void)
1232{
1233 gdouble p;
1234
1235 p = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(gl.wv));
1236 gtk_entry_set_progress_fraction(GTK_ENTRY(gl.entry), (p == 1 ? 0 : p));
1237}
1238
1239void
1240update_title(void)
1241{
1242 const gchar *t;
1243 gchar *u;
1244
1245 u = get_uri(gl.wv);
1246 t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(gl.wv));
1247
1248 /* title priority: title, url, __NAME__ */
1249 t = !t || t[0] == 0 ? (!u || u[0] == 0 ? __NAME__ : u) : t;
1250
1251 gtk_entry_set_icon_from_icon_name(
1252 GTK_ENTRY(gl.entry),
1253 GTK_ENTRY_ICON_PRIMARY,
1254 gl.failed_crt ? ICON_BAD_TLS
1255 : gl.https && gl.tls_error != G_TLS_CERTIFICATE_NO_FLAGS ? ICON_BAD_TLS
1256 : gl.https ? ICON_TLS
1257 : NULL
1258 );
1259
1260 gtk_window_set_title(GTK_WINDOW(gl.win), t);
1261}
1262
1263void
1264uri_changed(void)
1265{
1266 gchar *t = NULL;
1267
1268 /* make sure to not overwrite the "WEB PROCESS CRASHED" message */
1269 if ((t = get_uri(gl.wv)) && strlen(t) > 0) {
1270 gtk_entry_set_text(GTK_ENTRY(gl.entry), t);
1271 save_history(t);
1272 }
1273
1274 g_free(t);
1275}
1276
1277void
1278web_view_crashed(void)
1279{
1280 gchar *t = NULL,
1281 *u = NULL;
1282
1283 gtk_entry_set_text(
1284 GTK_ENTRY(gl.entry),
1285 (t = g_strdup_printf("WEB PROCESS CRASHED: %s", (u = get_uri(gl.wv))))
1286 );
1287 g_free_all((gpointer[]){t, u, NULL});
1288}
1289
1290void
1291xdg_open(
1292 const gchar *s,
1293 const gchar *t,
1294 gboolean keep)
1295{
1296 char b[URI_MAX]; /* command line buffer */
1297
1298 if (fork() == 0) {
1299 /* if not keep, make sure to send the scheme the way it was matched */
1300 sprintf(b, "%s%s", s, keep ? t : t+strlen(s));
1301 execvp(XDG_OPEN, (char *[]){ XDG_OPEN, b, NULL });
1302 fprintf(stderr, "child process (execvp) failed: %s %s\n", XDG_OPEN, b);
1303 exit(EXIT_FAILURE);
1304 }
1305}
1306
1307int
1308main(
1309 int argc,
1310 char **argv)
1311{
1312 int opt,
1313 i;
1314
1315 sigchld(0);
1316
1317 gl.arg0 = argv[0];
1318 gtk_init(&argc, &argv);
1319
1320 /* load default configuration before reading command-line arguments */
1321 load_configuration();
1322
1323 while ((opt = getopt(argc, argv, "E:")) != -1)
1324 switch (opt) {
1325 case 'E': to_win(&gl.embed, optarg); break;
1326 default: die("Usage: " __NAME__ " [-E WINID] [URI ...] [FILE ...]\n");
1327 }
1328
1329 set_default_web_context();
1330 main_window_setup();
1331
1332 /* load a default home uri if no clients and no arguments exist */
1333 if (optind >= argc)
1334 initialize(CFG_S(CFN_HOME_URI));
1335 /* load stdin if first argument is '-' */
1336 if (optind < argc && !strcmp(argv[optind], "-"))
1337 load_stdin();
1338 /* load remaining command line arguments as uris into new clients */
1339 else if (optind < argc)
1340 for (i = optind; i < argc; initialize(argv[i++]));
1341
1342 gtk_main();
1343
1344 exit(EXIT_SUCCESS);
1345}