Replace initdownload() and intercept global download requests.
[surf.git] / surf.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * To understand surf, start reading main().
4  */
5 #include <signal.h>
6 #include <X11/X.h>
7 #include <X11/Xatom.h>
8 #include <gtk/gtkx.h>
9 #include <gtk/gtk.h>
10 #include <gdk/gdkx.h>
11 #include <gdk/gdk.h>
12 #include <gdk/gdkkeysyms.h>
13 #include <string.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17 #include <limits.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <webkit2/webkit2.h>
21 #include <glib/gstdio.h>
22 #include <JavaScriptCore/JavaScript.h>
23 #include <sys/file.h>
24 #include <libgen.h>
25 #include <stdarg.h>
26 #include <regex.h>
27 #include <pwd.h>
28 #include <string.h>
29
30 #include "arg.h"
31
32 char *argv0;
33
34 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
35 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
36
37 enum { AtomFind, AtomGo, AtomUri, AtomLast };
38
39 enum {
40         OnDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
41         OnLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
42         OnImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
43         OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
44         OnEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
45         OnBar   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
46         OnSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
47         OnAny   = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
48 };
49
50 typedef union Arg Arg;
51 union Arg {
52         gboolean b;
53         gint i;
54         const void *v;
55 };
56
57 typedef struct Client {
58         GtkWidget *win;
59         Window xid;
60         WebKitWebView *view;
61         WebKitWebInspector *inspector;
62         WebKitHitTestResult *mousepos;
63         GTlsCertificateFlags tlsflags;
64         const char *title, *targeturi;
65         const char *needle;
66         gint progress;
67         struct Client *next;
68         gboolean zoomed, fullscreen, isinspecting;
69 } Client;
70
71 typedef struct {
72         guint mod;
73         guint keyval;
74         void (*func)(Client *c, const Arg *arg);
75         const Arg arg;
76 } Key;
77
78 typedef struct {
79         unsigned int click;
80         unsigned int mask;
81         guint button;
82         void (*func)(Client *c, const Arg *arg);
83         const Arg arg;
84 } Button;
85
86 typedef struct {
87         char *regex;
88         char *style;
89         regex_t re;
90 } SiteStyle;
91
92 static Display *dpy;
93 static Atom atoms[AtomLast];
94 static Client *clients = NULL;
95 static Window embed = 0;
96 static gboolean showxid = FALSE;
97 static char winid[64];
98 static char togglestat[9];
99 static char pagestat[3];
100 static GTlsDatabase *tlsdb;
101 static int cookiepolicy;
102 static char *stylefile = NULL;
103
104 static void addaccelgroup(Client *c);
105 static void beforerequest(WebKitWebView *w, WebKitWebFrame *f,
106                           WebKitWebResource *r, WebKitNetworkRequest *req,
107                           WebKitNetworkResponse *resp, Client *c);
108 static char *buildfile(const char *path);
109 static char *buildpath(const char *path);
110 static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c);
111 static void cleanup(void);
112 static void clipboard(Client *c, const Arg *arg);
113
114 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
115 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
116
117 static char *copystr(char **str, const char *src);
118 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
119                 Client *c);
120 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
121     WebKitPolicyDecisionType dt, Client *c);
122 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
123 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
124 static void decideresource(WebKitPolicyDecision *d, Client *c);
125 static gboolean deletion_interface(WebKitWebView *view,
126                                    WebKitDOMHTMLElement *arg1, Client *c);
127 static void destroyclient(Client *c);
128 static void destroywin(GtkWidget* w, Client *c);
129 static void die(const char *errstr, ...);
130 static void eval(Client *c, const Arg *arg);
131 static void find(Client *c, const Arg *arg);
132 static void fullscreen(Client *c, const Arg *arg);
133 static gboolean permissionrequested(WebKitWebView *v,
134                 WebKitPermissionRequest *r, Client *c);
135 static const char *getatom(Client *c, int a);
136 static void gettogglestat(Client *c);
137 static void getpagestat(Client *c);
138 static char *geturi(Client *c);
139 static const gchar *getstyle(const char *uri);
140 static void setstyle(Client *c, const char *style);
141
142 static void handleplumb(Client *c, WebKitWebView *w, const gchar *uri);
143
144 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
145                 Client *c);
146 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
147 static void download(Client *c, WebKitURIResponse *r);
148
149 static void inspector(Client *c, const Arg *arg);
150 static WebKitWebView *inspector_new(WebKitWebInspector *i, WebKitWebView *v,
151                                     Client *c);
152 static gboolean inspector_show(WebKitWebInspector *i, Client *c);
153 static gboolean inspector_close(WebKitWebInspector *i, Client *c);
154 static void inspector_finished(WebKitWebInspector *i, Client *c);
155
156 static gboolean keypress(GtkAccelGroup *group, GObject *obj, guint key,
157                          GdkModifierType mods, Client *c);
158 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
159                 guint modifiers, Client *c);
160 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
161 static void loaduri(Client *c, const Arg *arg);
162 static void navigate(Client *c, const Arg *arg);
163 static Client *newclient(Client *c);
164 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
165 static void showview(WebKitWebView *v, Client *c);
166 static void newwindow(Client *c, const Arg *arg, gboolean noembed);
167 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
168 static gboolean contextmenu(WebKitWebView *view, GtkWidget *menu,
169                             WebKitHitTestResult *target, gboolean keyboard,
170                             Client *c);
171 static void menuactivate(GtkMenuItem *item, Client *c);
172 static void print(Client *c, const Arg *arg);
173 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
174                                 gpointer d);
175 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
176 static void linkopen(Client *c, const Arg *arg);
177 static void linkopenembed(Client *c, const Arg *arg);
178 static void reload(Client *c, const Arg *arg);
179 static void scroll_h(Client *c, const Arg *arg);
180 static void scroll_v(Client *c, const Arg *arg);
181 static void scroll(GtkAdjustment *a, const Arg *arg);
182 static void setatom(Client *c, int a, const char *v);
183 static void setup(void);
184 static void sigchld(int unused);
185 static void spawn(Client *c, const Arg *arg);
186 static void stop(Client *c, const Arg *arg);
187 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
188 static void titlechangeleave(void *a, void *b, Client *c);
189 static void toggle(Client *c, const Arg *arg);
190 static void togglecookiepolicy(Client *c, const Arg *arg);
191 static void togglegeolocation(Client *c, const Arg *arg);
192 static void togglescrollbars(Client *c, const Arg *arg);
193 static void togglestyle(Client *c, const Arg *arg);
194 static void updatetitle(Client *c);
195 static void updatewinid(Client *c);
196 static void usage(void);
197 static void windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame,
198                                 JSContextRef js, JSObjectRef win, Client *c);
199 static void zoom(Client *c, const Arg *arg);
200
201 /* configuration, allows nested code to access above variables */
202 #include "config.h"
203
204 void
205 addaccelgroup(Client *c)
206 {
207         int i;
208         GtkAccelGroup *group = gtk_accel_group_new();
209         GClosure *closure;
210
211         for (i = 0; i < LENGTH(keys); i++) {
212                 closure = g_cclosure_new(G_CALLBACK(keypress), c, NULL);
213                 gtk_accel_group_connect(group, keys[i].keyval, keys[i].mod, 0,
214                                         closure);
215         }
216         gtk_window_add_accel_group(GTK_WINDOW(c->win), group);
217 }
218
219 void
220 beforerequest(WebKitWebView *w, WebKitWebFrame *f, WebKitWebResource *r,
221               WebKitNetworkRequest *req, WebKitNetworkResponse *resp,
222               Client *c)
223 {
224         const gchar *uri = webkit_network_request_get_uri(req);
225         int i, isascii = 1;
226
227         if (g_str_has_suffix(uri, "/favicon.ico"))
228                 webkit_network_request_set_uri(req, "about:blank");
229
230         if (!g_str_has_prefix(uri, "http://")
231             && !g_str_has_prefix(uri, "https://")
232             && !g_str_has_prefix(uri, "about:")
233             && !g_str_has_prefix(uri, "file://")
234             && !g_str_has_prefix(uri, "data:")
235             && !g_str_has_prefix(uri, "blob:")
236             && strlen(uri) > 0) {
237                 for (i = 0; i < strlen(uri); i++) {
238                         if (!g_ascii_isprint(uri[i])) {
239                                 isascii = 0;
240                                 break;
241                         }
242                 }
243                 if (isascii)
244                         handleplumb(c, w, uri);
245         }
246 }
247
248 char *
249 buildfile(const char *path)
250 {
251         char *dname, *bname, *bpath, *fpath;
252         FILE *f;
253
254         dname = g_path_get_dirname(path);
255         bname = g_path_get_basename(path);
256
257         bpath = buildpath(dname);
258         g_free(dname);
259
260         fpath = g_build_filename(bpath, bname, NULL);
261         g_free(bpath);
262         g_free(bname);
263
264         if (!(f = fopen(fpath, "a")))
265                 die("Could not open file: %s\n", fpath);
266
267         g_chmod(fpath, 0600); /* always */
268         fclose(f);
269
270         return fpath;
271 }
272
273 char *
274 buildpath(const char *path)
275 {
276         struct passwd *pw;
277         char *apath, *name, *p, *fpath;
278
279         if (path[0] == '~') {
280                 if (path[1] == '/' || path[1] == '\0') {
281                         p = (char *)&path[1];
282                         pw = getpwuid(getuid());
283                 } else {
284                         if ((p = strchr(path, '/')))
285                                 name = g_strndup(&path[1], --p - path);
286                         else
287                                 name = g_strdup(&path[1]);
288
289                         if (!(pw = getpwnam(name))) {
290                                 die("Can't get user %s home directory: %s.\n",
291                                     name, path);
292                         }
293                         g_free(name);
294                 }
295                 apath = g_build_filename(pw->pw_dir, p, NULL);
296         } else {
297                 apath = g_strdup(path);
298         }
299
300         /* creating directory */
301         if (g_mkdir_with_parents(apath, 0700) < 0)
302                 die("Could not access directory: %s\n", apath);
303
304         fpath = realpath(apath, NULL);
305         g_free(apath);
306
307         return fpath;
308 }
309
310 gboolean
311 buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c)
312 {
313         WebKitHitTestResultContext context;
314         WebKitHitTestResult *result;
315         Arg arg;
316         unsigned int i;
317
318         result = webkit_web_view_get_hit_test_result(web, e);
319         g_object_get(result, "context", &context, NULL);
320         g_object_get(result, "link-uri", &arg.v, NULL);
321         for (i = 0; i < LENGTH(buttons); i++) {
322                 if (context & buttons[i].click
323                     && e->button == buttons[i].button
324                     && CLEANMASK(e->state) == CLEANMASK(buttons[i].mask)
325                     && buttons[i].func) {
326                         buttons[i].func(c, buttons[i].click == ClkLink
327                             && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
328                         return true;
329                 }
330         }
331         return false;
332 }
333
334 void
335 cleanup(void)
336 {
337         while (clients)
338                 destroyclient(clients);
339         g_free(cookiefile);
340         g_free(scriptfile);
341         g_free(stylefile);
342         g_free(cachedir);
343 }
344
345 WebKitCookieAcceptPolicy
346 cookiepolicy_get(void)
347 {
348         switch (cookiepolicies[cookiepolicy]) {
349         case 'a':
350                 return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
351         case '@':
352                 return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
353         case 'A':
354         default:
355                 break;
356         }
357
358         return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
359 }
360
361 char
362 cookiepolicy_set(const WebKitCookieAcceptPolicy ep)
363 {
364         switch (ep) {
365         case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
366                 return 'a';
367         case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
368                 return '@';
369         case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
370         default:
371                 break;
372         }
373
374         return 'A';
375 }
376
377 void
378 evalscript(JSContextRef js, char *script, char* scriptname)
379 {
380         JSStringRef jsscript, jsscriptname;
381         JSValueRef exception = NULL;
382
383         jsscript = JSStringCreateWithUTF8CString(script);
384         jsscriptname = JSStringCreateWithUTF8CString(scriptname);
385         JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js),
386                          jsscriptname, 0, &exception);
387         JSStringRelease(jsscript);
388         JSStringRelease(jsscriptname);
389 }
390
391 void
392 runscript(WebKitWebFrame *frame)
393 {
394         char *script;
395         GError *error;
396
397         if (g_file_get_contents(scriptfile, &script, NULL, &error)) {
398                 evalscript(webkit_web_frame_get_global_context(frame), script,
399                            scriptfile);
400         }
401 }
402
403 void
404 clipboard(Client *c, const Arg *arg)
405 {
406         gboolean paste = *(gboolean *)arg;
407
408         if (paste) {
409                 gtk_clipboard_request_text(gtk_clipboard_get(
410                                            GDK_SELECTION_PRIMARY),
411                                            pasteuri, c);
412         } else {
413                 gtk_clipboard_set_text(gtk_clipboard_get(
414                                        GDK_SELECTION_PRIMARY), c->linkhover
415                                        ? c->linkhover : geturi(c), -1);
416         }
417 }
418
419 char *
420 copystr(char **str, const char *src)
421 {
422         char *tmp;
423         tmp = g_strdup(src);
424
425         if (str && *str) {
426                 g_free(*str);
427                 *str = tmp;
428         }
429         return tmp;
430 }
431
432 GtkWidget *
433 createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
434 {
435         Client *n;
436
437         switch (webkit_navigation_action_get_navigation_type(a)) {
438         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
439                 /*
440                  * popup windows of type “other” are almost always triggered
441                  * by user gesture, so inverse the logic here
442                  */
443 /* instead of this, compare destination uri to mouse-over uri for validating window */
444                 if (webkit_navigation_action_is_user_gesture(a)) {
445                         return NULL;
446                         break;
447                 }
448         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
449         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
450         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
451         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
452         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
453                 n = newclient(c);
454                 break;
455         default:
456                 return NULL;
457                 break;
458         }
459
460         return GTK_WIDGET(n->view);
461 }
462
463 gboolean
464 decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
465     WebKitPolicyDecisionType dt, Client *c)
466 {
467         switch (dt) {
468         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
469                 decidenavigation(d, c);
470                 break;
471         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
472                 decidenewwindow(d, c);
473                 break;
474         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
475                 decideresource(d, c);
476                 break;
477         default:
478                 webkit_policy_decision_ignore(d);
479                 break;
480         }
481         return TRUE;
482 }
483
484 void
485 decidenavigation(WebKitPolicyDecision *d, Client *c)
486 {
487         WebKitNavigationAction *a;
488
489         a = webkit_navigation_policy_decision_get_navigation_action(
490             WEBKIT_NAVIGATION_POLICY_DECISION(d));
491
492         switch (webkit_navigation_action_get_navigation_type(a)) {
493         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
494         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
495         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
496         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
497         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
498         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
499         default:
500                 /* Do not navigate to links with a "_blank" target (popup) */
501                 if (webkit_navigation_policy_decision_get_frame_name(
502                     WEBKIT_NAVIGATION_POLICY_DECISION(d))) {
503                         webkit_policy_decision_ignore(d);
504                 } else {
505                         /* Filter out navigation to different domain ? */
506                         /* get action→urirequest, copy and load in new window+view
507                          * on Ctrl+Click ? */
508                         webkit_policy_decision_use(d);
509                 }
510                 break;
511         }
512 }
513
514 void
515 decidenewwindow(WebKitPolicyDecision *d, Client *c)
516 {
517         WebKitNavigationAction *a;
518         Arg arg;
519
520         a = webkit_navigation_policy_decision_get_navigation_action(
521             WEBKIT_NAVIGATION_POLICY_DECISION(d));
522
523         switch (webkit_navigation_action_get_navigation_type(a)) {
524         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
525         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
526         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
527         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
528         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
529                 /* Filter domains here */
530 /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
531  * test for link clicked but no button ? */
532                 arg.v = webkit_uri_request_get_uri(
533                     webkit_navigation_action_get_request(a));
534                 newwindow(c, &arg, 0);
535                 break;
536         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
537         default:
538                 break;
539         }
540
541         webkit_policy_decision_ignore(d);
542 }
543
544 void
545 decideresource(WebKitPolicyDecision *d, Client *c)
546 {
547         WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
548         WebKitURIResponse *res;
549
550         if (webkit_response_policy_decision_is_mime_type_supported(r)) {
551                 webkit_policy_decision_use(d);
552         } else {
553 res = webkit_response_policy_decision_get_response(r);
554                 webkit_policy_decision_ignore(d);
555                 download(c, res);
556         }
557 }
558
559 gboolean
560 deletion_interface(WebKitWebView *view, WebKitDOMHTMLElement *arg1, Client *c)
561 {
562         return FALSE;
563 }
564
565 void
566 destroyclient(Client *c)
567 {
568         Client *p;
569
570         webkit_web_view_stop_loading(c->view);
571         gtk_widget_destroy(GTK_WIDGET(c->view));
572         gtk_widget_destroy(c->scroll);
573         gtk_widget_destroy(c->vbox);
574         gtk_widget_destroy(c->win);
575
576         for (p = clients; p && p->next != c; p = p->next)
577                 ;
578         if (p)
579                 p->next = c->next;
580         else
581                 clients = c->next;
582         free(c);
583         if (clients == NULL)
584                 gtk_main_quit();
585 }
586
587 void
588 destroywin(GtkWidget* w, Client *c)
589 {
590         destroyclient(c);
591 }
592
593 void
594 die(const char *errstr, ...)
595 {
596         va_list ap;
597
598         va_start(ap, errstr);
599         vfprintf(stderr, errstr, ap);
600         va_end(ap);
601         exit(EXIT_FAILURE);
602 }
603
604 void
605 find(Client *c, const Arg *arg)
606 {
607         const char *s;
608
609         s = getatom(c, AtomFind);
610         gboolean forward = *(gboolean *)arg;
611         webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE);
612 }
613
614 void
615 fullscreen(Client *c, const Arg *arg)
616 {
617         if (c->fullscreen)
618                 gtk_window_unfullscreen(GTK_WINDOW(c->win));
619         else
620                 gtk_window_fullscreen(GTK_WINDOW(c->win));
621         c->fullscreen = !c->fullscreen;
622 }
623
624 gboolean
625 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
626 {
627         if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
628                 if (allowgeolocation)
629                         webkit_permission_request_allow(r);
630                 else
631                         webkit_permission_request_deny(r);
632                 return TRUE;
633         }
634
635         return FALSE;
636 }
637
638 const char *
639 getatom(Client *c, int a)
640 {
641         static char buf[BUFSIZ];
642         Atom adummy;
643         int idummy;
644         unsigned long ldummy;
645         unsigned char *p = NULL;
646
647         XGetWindowProperty(dpy, c->xid,
648                            atoms[a], 0L, BUFSIZ, False, XA_STRING,
649                            &adummy, &idummy, &ldummy, &ldummy, &p);
650         if (p)
651                 strncpy(buf, (char *)p, LENGTH(buf)-1);
652         else
653                 buf[0] = '\0';
654         XFree(p);
655
656         return buf;
657 }
658
659 char *
660 geturi(Client *c)
661 {
662         char *uri;
663
664         if (!(uri = (char *)webkit_web_view_get_uri(c->view)))
665                 uri = "about:blank";
666         return uri;
667 }
668
669 const gchar *
670 getstyle(const char *uri)
671 {
672         int i;
673
674         if (stylefile != NULL)
675                 return stylefile;
676
677         for (i = 0; i < LENGTH(styles); i++) {
678                 if (styles[i].regex && !regexec(&(styles[i].re), uri, 0,
679                     NULL, 0))
680                         return styles[i].style;
681         }
682
683         return "";
684 }
685
686 void
687 setstyle(Client *c, const char *style)
688 {
689         WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
690
691         g_object_set(G_OBJECT(settings), "user-stylesheet-uri", style, NULL);
692 }
693
694 void
695 handleplumb(Client *c, WebKitWebView *w, const gchar *uri)
696 {
697         Arg arg;
698
699         webkit_web_view_stop_loading(w);
700         arg = (Arg)PLUMB((char *)uri);
701         spawn(c, &arg);
702 }
703
704 void
705 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
706 {
707         g_signal_connect(G_OBJECT(d), "notify::response",
708             G_CALLBACK(responsereceived), c);
709 }
710
711 void
712 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
713 {
714         download(c, webkit_download_get_response(d));
715         webkit_download_cancel(d);
716 }
717
718 void
719 download(Client *c, WebKitURIResponse *r)
720 {
721         Arg a;
722
723         a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
724         spawn(c, &a);
725 }
726
727 void
728 inspector(Client *c, const Arg *arg)
729 {
730         if (enableinspector) {
731                 if (c->isinspecting)
732                         webkit_web_inspector_close(c->inspector);
733                 else
734                         webkit_web_inspector_show(c->inspector);
735         }
736 }
737
738 WebKitWebView *
739 inspector_new(WebKitWebInspector *i, WebKitWebView *v, Client *c)
740 {
741         return WEBKIT_WEB_VIEW(webkit_web_view_new());
742 }
743
744 gboolean
745 inspector_show(WebKitWebInspector *i, Client *c)
746 {
747         WebKitWebView *w;
748
749         if (c->isinspecting)
750                 return false;
751
752         w = webkit_web_inspector_get_web_view(i);
753         gtk_paned_pack2(GTK_PANED(c->pane), GTK_WIDGET(w), TRUE, TRUE);
754         gtk_widget_show(GTK_WIDGET(w));
755         c->isinspecting = true;
756
757         return true;
758 }
759
760 gboolean
761 inspector_close(WebKitWebInspector *i, Client *c)
762 {
763         GtkWidget *w;
764
765         if (!c->isinspecting)
766                 return false;
767
768         w = GTK_WIDGET(webkit_web_inspector_get_web_view(i));
769         gtk_widget_hide(w);
770         gtk_widget_destroy(w);
771         c->isinspecting = false;
772
773         return true;
774 }
775
776 void
777 inspector_finished(WebKitWebInspector *i, Client *c)
778 {
779         g_free(c->inspector);
780 }
781
782 gboolean
783 keypress(GtkAccelGroup *group, GObject *obj, guint key, GdkModifierType mods,
784          Client *c)
785 {
786         guint i;
787         gboolean processed = FALSE;
788
789         mods = CLEANMASK(mods);
790         key = gdk_keyval_to_lower(key);
791         updatewinid(c);
792         for (i = 0; i < LENGTH(keys); i++) {
793                 if (key == keys[i].keyval
794                     && mods == keys[i].mod
795                     && keys[i].func) {
796                         keys[i].func(c, &(keys[i].arg));
797                         processed = TRUE;
798                 }
799         }
800
801         return processed;
802 }
803
804 void
805 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
806     Client *c)
807 {
808         WebKitHitTestResultContext hc;
809
810         /* Keep the hit test to know where is the pointer on the next click */
811         c->mousepos = h;
812
813         hc = webkit_hit_test_result_get_context(h);
814
815         if (hc & OnLink)
816                 c->targeturi = webkit_hit_test_result_get_link_uri(h);
817         else if (hc & OnImg)
818                 c->targeturi = webkit_hit_test_result_get_image_uri(h);
819         else if (hc & OnMedia)
820                 c->targeturi = webkit_hit_test_result_get_media_uri(h);
821         else
822                 c->targeturi = NULL;
823         updatetitle(c);
824 }
825
826 void
827 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
828 {
829         switch (e) {
830         case WEBKIT_LOAD_STARTED:
831                 c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
832                 break;
833         case WEBKIT_LOAD_REDIRECTED:
834                 setatom(c, AtomUri, geturi(c));
835                 break;
836         case WEBKIT_LOAD_COMMITTED:
837                 if (!webkit_web_view_get_tls_info(c->view, NULL, &(c->tlsflags)))
838                         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
839
840                 setatom(c, AtomUri, geturi(c));
841
842                 if (enablestyle)
843                         setstyle(c, getstyle(geturi(c)));
844                 break;
845         case WEBKIT_LOAD_FINISHED:
846                 /* Disabled until we write some WebKitWebExtension for
847                  * manipulating the DOM directly.
848                 evalscript(c, "document.documentElement.style.overflow = '%s'",
849                     enablescrollbars ? "auto" : "hidden");
850                 */
851                 runscript(c);
852                 break;
853         }
854         updatetitle(c);
855 }
856
857 void
858 loaduri(Client *c, const Arg *arg)
859 {
860         char *u = NULL, *rp;
861         const char *uri = (char *)arg->v;
862         Arg a = { .b = FALSE };
863         struct stat st;
864
865         if (strcmp(uri, "") == 0)
866                 return;
867
868         /* In case it's a file path. */
869         if (stat(uri, &st) == 0) {
870                 rp = realpath(uri, NULL);
871                 u = g_strdup_printf("file://%s", rp);
872                 free(rp);
873         } else {
874                 u = g_strrstr(uri, "://") ? g_strdup(uri)
875                     : g_strdup_printf("http://%s", uri);
876         }
877
878         setatom(c, AtomUri, uri);
879
880         /* prevents endless loop */
881         if (strcmp(u, geturi(c)) == 0) {
882                 reload(c, &a);
883         } else {
884                 webkit_web_view_load_uri(c->view, u);
885                 c->progress = 0;
886                 c->title = copystr(&c->title, u);
887                 updatetitle(c);
888         }
889         g_free(u);
890 }
891
892 void
893 navigate(Client *c, const Arg *arg)
894 {
895         int steps = *(int *)arg;
896         webkit_web_view_go_back_or_forward(c->view, steps);
897 }
898
899 Client *
900 newclient(Client *rc)
901 {
902         Client *c;
903         gdouble dpi;
904
905         if (!(c = calloc(1, sizeof(Client))))
906                 die("Cannot malloc!\n");
907
908         c->title = NULL;
909         c->progress = 100;
910
911         c->next = clients;
912         clients = c;
913
914         c->view = newview(c, rc ? rc->view : NULL);
915         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
916
917         return c;
918 }
919
920 WebKitWebView *
921 newview(Client *c, WebKitWebView *rv)
922 {
923         WebKitWebView *v;
924         WebKitSettings *settings;
925         WebKitUserContentManager *contentmanager;
926         WebKitWebContext *context;
927         char *ua;
928
929         /* Webview */
930         if (rv) {
931                 v = WEBKIT_WEB_VIEW(
932                     webkit_web_view_new_with_related_view(rv));
933         } else {
934                 settings = webkit_settings_new_with_settings(
935                     "auto-load-images", loadimages,
936                     "default-font-size", defaultfontsize,
937                     "enable-caret-browsing", enablecaretbrowsing,
938                     "enable-developer-extras", enableinspector,
939                     "enable-dns-prefetching", enablednsprefetching,
940                     "enable-frame-flattening", enableframeflattening,
941                     "enable-html5-database", enablecache,
942                     "enable-html5-local-storage", enablecache,
943                     "enable-javascript", enablescripts,
944                     "enable-plugins", enableplugins,
945                     NULL);
946                 if (!(ua = getenv("SURF_USERAGENT")))
947                         ua = useragent;
948                 webkit_settings_set_user_agent(settings, ua);
949                 /* Have a look at http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html
950                  * for more interesting settings */
951
952                 contentmanager = webkit_user_content_manager_new();
953
954                 context = webkit_web_context_new_with_website_data_manager(
955                     webkit_website_data_manager_new(
956                     "base-cache-directory", cachedir,
957                     "base-data-directory", cachedir,
958                     NULL));
959
960                 /* rendering process model, can be a shared unique one or one for each
961                  * view */
962                 webkit_web_context_set_process_model(context,
963                     WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
964                 /* ssl */
965                 webkit_web_context_set_tls_errors_policy(context, strictssl ?
966                     WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
967                 /* disk cache */
968                 webkit_web_context_set_cache_model(context, enablecache ?
969                     WEBKIT_CACHE_MODEL_WEB_BROWSER : WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
970
971                 /* Currently only works with text file to be compatible with curl */
972                 webkit_cookie_manager_set_persistent_storage(
973                     webkit_web_context_get_cookie_manager(context), cookiefile,
974                     WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
975                 /* cookie policy */
976                 webkit_cookie_manager_set_accept_policy(
977                     webkit_web_context_get_cookie_manager(context),
978                     cookiepolicy_get());
979
980                 g_signal_connect(G_OBJECT(context), "download-started",
981                     G_CALLBACK(downloadstarted), c);
982
983                 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
984                     "settings", settings,
985                     "user-content-manager", contentmanager,
986                     "web-context", context,
987                     NULL);
988         }
989
990         g_signal_connect(G_OBJECT(v),
991                          "notify::title",
992                          G_CALLBACK(titlechanged), c);
993         g_signal_connect(G_OBJECT(v),
994                          "mouse-target-changed",
995                          G_CALLBACK(mousetargetchanged), c);
996         g_signal_connect(G_OBJECT(v),
997                          "permission-request",
998                          G_CALLBACK(permissionrequested), c);
999         g_signal_connect(G_OBJECT(v),
1000                          "create",
1001                          G_CALLBACK(createview), c);
1002         g_signal_connect(G_OBJECT(v), "ready-to-show",
1003                          G_CALLBACK(showview), c);
1004         g_signal_connect(G_OBJECT(v),
1005                          "decide-policy",
1006                          G_CALLBACK(decidepolicy), c);
1007         g_signal_connect(G_OBJECT(v),
1008                          "window-object-cleared",
1009                          G_CALLBACK(windowobjectcleared), c);
1010         g_signal_connect(G_OBJECT(v),
1011                          "load-changed",
1012                          G_CALLBACK(loadchanged), c);
1013         g_signal_connect(G_OBJECT(v),
1014                          "notify::estimated-load-progress",
1015                          G_CALLBACK(progresschanged), c);
1016         g_signal_connect(G_OBJECT(v),
1017                          "button-release-event",
1018                          G_CALLBACK(buttonrelease), c);
1019         g_signal_connect(G_OBJECT(v),
1020                          "context-menu",
1021                          G_CALLBACK(contextmenu), c);
1022         g_signal_connect(G_OBJECT(v),
1023                          "resource-request-starting",
1024                          G_CALLBACK(beforerequest), c);
1025         g_signal_connect(G_OBJECT(v),
1026                          "should-show-delete-interface-for-element",
1027                          G_CALLBACK(deletion_interface), c);
1028
1029         return v;
1030 }
1031
1032 void
1033 showview(WebKitWebView *v, Client *c)
1034 {
1035         GdkGeometry hints = { 1, 1 };
1036         GdkRGBA bgcolor = { 0 };
1037         GdkWindow *gwin;
1038
1039         /* Window */
1040         if (embed) {
1041                 c->win = gtk_plug_new(embed);
1042         } else {
1043                 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1044
1045                 /* TA:  20091214:  Despite what the GNOME docs say, the ICCCM
1046                  * is always correct, so we should still call this function.
1047                  * But when doing so, we *must* differentiate between a
1048                  * WM_CLASS and a resource on the window.  By convention, the
1049                  * window class (WM_CLASS) is capped, while the resource is in
1050                  * lowercase.   Both these values come as a pair.
1051                  */
1052                 gtk_window_set_wmclass(GTK_WINDOW(c->win), "surf", "Surf");
1053
1054                 /* TA:  20091214:  And set the role here as well -- so that
1055                  * sessions can pick this up.
1056                  */
1057                 gtk_window_set_role(GTK_WINDOW(c->win), "Surf");
1058         }
1059         gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600);
1060         g_signal_connect(G_OBJECT(c->win),
1061                          "destroy",
1062                          G_CALLBACK(destroywin), c);
1063         g_signal_connect(G_OBJECT(c->win),
1064                          "leave_notify_event",
1065                          G_CALLBACK(titlechangeleave), c);
1066
1067         if (!kioskmode)
1068                 addaccelgroup(c);
1069
1070         /* Arranging */
1071         gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
1072
1073         /* Setup */
1074         gtk_widget_grab_focus(GTK_WIDGET(c->view));
1075         gtk_widget_show(GTK_WIDGET(c->view));
1076         gtk_widget_show(c->win);
1077         gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
1078         c->xid = gdk_x11_window_get_xid(gwin);
1079         gtk_window_set_geometry_hints(GTK_WINDOW(c->win), NULL, &hints,
1080                                       GDK_HINT_MIN_SIZE);
1081         gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
1082         gdk_window_add_filter(gwin, processx, c);
1083
1084         runscript(frame);
1085
1086         /* This might conflict with _zoomto96dpi_. */
1087         if (zoomlevel != 1.0)
1088                 webkit_web_view_set_zoom_level(c->view, zoomlevel);
1089
1090         if (runinfullscreen)
1091                 fullscreen(c, NULL);
1092
1093         setatom(c, AtomFind, "");
1094         setatom(c, AtomUri, "about:blank");
1095         if (hidebackground)
1096                 webkit_web_view_set_background_color(c->view, &bgcolor);
1097
1098         if (showxid) {
1099                 gdk_display_sync(gtk_widget_get_display(c->win));
1100                 printf("%lu\n", c->xid);
1101                 fflush(NULL);
1102                 if (fclose(stdout) != 0) {
1103                         die("Error closing stdout");
1104                 }
1105         }
1106 }
1107
1108 void
1109 newwindow(Client *c, const Arg *arg, gboolean noembed)
1110 {
1111         guint i = 0;
1112         const char *cmd[18], *uri;
1113         const Arg a = { .v = (void *)cmd };
1114         char tmp[64];
1115
1116         cmd[i++] = argv0;
1117         cmd[i++] = "-a";
1118         cmd[i++] = cookiepolicies;
1119         if (!enablescrollbars)
1120                 cmd[i++] = "-b";
1121         if (embed && !noembed) {
1122                 cmd[i++] = "-e";
1123                 snprintf(tmp, LENGTH(tmp), "%u", (int)embed);
1124                 cmd[i++] = tmp;
1125         }
1126         if (!allowgeolocation)
1127                 cmd[i++] = "-g";
1128         if (!loadimages)
1129                 cmd[i++] = "-i";
1130         if (kioskmode)
1131                 cmd[i++] = "-k";
1132         if (!enableplugins)
1133                 cmd[i++] = "-p";
1134         if (!enablescripts)
1135                 cmd[i++] = "-s";
1136         if (showxid)
1137                 cmd[i++] = "-x";
1138         if (enablediskcache)
1139                 cmd[i++] = "-D";
1140         cmd[i++] = "-c";
1141         cmd[i++] = cookiefile;
1142         cmd[i++] = "--";
1143         uri = arg->v ? (char *)arg->v : c->linkhover;
1144         if (uri)
1145                 cmd[i++] = uri;
1146         cmd[i++] = NULL;
1147         spawn(NULL, &a);
1148 }
1149
1150 gboolean
1151 contextmenu(WebKitWebView *view, GtkWidget *menu, WebKitHitTestResult *target,
1152             gboolean keyboard, Client *c)
1153 {
1154         GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu)));
1155
1156         for (GList *l = items; l; l = l->next)
1157                 g_signal_connect(l->data, "activate", G_CALLBACK(menuactivate), c);
1158
1159         g_list_free(items);
1160         return FALSE;
1161 }
1162
1163 void
1164 menuactivate(GtkMenuItem *item, Client *c)
1165 {
1166         /*
1167          * context-menu-action-2000 open link
1168          * context-menu-action-1    open link in window
1169          * context-menu-action-2    download linked file
1170          * context-menu-action-3    copy link location
1171          * context-menu-action-7    copy image address
1172          * context-menu-action-13   reload
1173          * context-menu-action-10   back
1174          * context-menu-action-11   forward
1175          * context-menu-action-12   stop
1176          */
1177
1178         const gchar *name, *uri;
1179         GtkClipboard *prisel, *clpbrd;
1180
1181         name = gtk_actionable_get_action_name(GTK_ACTIONABLE(item));
1182         if (name == NULL)
1183                 return;
1184
1185         if (!g_strcmp0(name, "context-menu-action-3")) {
1186                 prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1187                 gtk_clipboard_set_text(prisel, c->linkhover, -1);
1188         } else if (!g_strcmp0(name, "context-menu-action-7")) {
1189                 prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1190                 clpbrd = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
1191                 uri = gtk_clipboard_wait_for_text(clpbrd);
1192                 if (uri)
1193                         gtk_clipboard_set_text(prisel, uri, -1);
1194         }
1195 }
1196
1197 void
1198 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1199 {
1200         Arg arg = {.v = text };
1201         if (text != NULL)
1202                 loaduri((Client *) d, &arg);
1203 }
1204
1205 void
1206 print(Client *c, const Arg *arg)
1207 {
1208         webkit_web_frame_print(webkit_web_view_get_main_frame(c->view));
1209 }
1210
1211 GdkFilterReturn
1212 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
1213 {
1214         Client *c = (Client *)d;
1215         XPropertyEvent *ev;
1216         Arg arg;
1217
1218         if (((XEvent *)e)->type == PropertyNotify) {
1219                 ev = &((XEvent *)e)->xproperty;
1220                 if (ev->state == PropertyNewValue) {
1221                         if (ev->atom == atoms[AtomFind]) {
1222                                 arg.b = TRUE;
1223                                 find(c, &arg);
1224
1225                                 return GDK_FILTER_REMOVE;
1226                         } else if (ev->atom == atoms[AtomGo]) {
1227                                 arg.v = getatom(c, AtomGo);
1228                                 loaduri(c, &arg);
1229
1230                                 return GDK_FILTER_REMOVE;
1231                         }
1232                 }
1233         }
1234         return GDK_FILTER_CONTINUE;
1235 }
1236
1237 void
1238 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1239 {
1240         c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
1241             100;
1242         updatetitle(c);
1243 }
1244
1245 void
1246 linkopen(Client *c, const Arg *arg)
1247 {
1248         newwindow(NULL, arg, 1);
1249 }
1250
1251 void
1252 linkopenembed(Client *c, const Arg *arg)
1253 {
1254         newwindow(NULL, arg, 0);
1255 }
1256
1257 void
1258 reload(Client *c, const Arg *arg)
1259 {
1260         gboolean nocache = *(gboolean *)arg;
1261         if (nocache)
1262                 webkit_web_view_reload_bypass_cache(c->view);
1263         else
1264                 webkit_web_view_reload(c->view);
1265 }
1266
1267 void
1268 scroll_h(Client *c, const Arg *arg)
1269 {
1270         scroll(gtk_scrolled_window_get_hadjustment(
1271                GTK_SCROLLED_WINDOW(c->scroll)), arg);
1272 }
1273
1274 void
1275 scroll_v(Client *c, const Arg *arg)
1276 {
1277         scroll(gtk_scrolled_window_get_vadjustment(
1278                GTK_SCROLLED_WINDOW(c->scroll)), arg);
1279 }
1280
1281 void
1282 scroll(GtkAdjustment *a, const Arg *arg)
1283 {
1284         gdouble v;
1285
1286         v = gtk_adjustment_get_value(a);
1287         switch (arg->i) {
1288         case +10000:
1289         case -10000:
1290                 v += gtk_adjustment_get_page_increment(a) * (arg->i / 10000);
1291                 break;
1292         case +20000:
1293         case -20000:
1294         default:
1295                 v += gtk_adjustment_get_step_increment(a) * arg->i;
1296         }
1297
1298         v = MAX(v, 0.0);
1299         v = MIN(v, gtk_adjustment_get_upper(a) -
1300                 gtk_adjustment_get_page_size(a));
1301         gtk_adjustment_set_value(a, v);
1302 }
1303
1304 void
1305 setatom(Client *c, int a, const char *v)
1306 {
1307         XSync(dpy, False);
1308         XChangeProperty(dpy, c->xid,
1309                         atoms[a], XA_STRING, 8, PropModeReplace,
1310                         (unsigned char *)v, strlen(v) + 1);
1311 }
1312
1313 void
1314 setup(void)
1315 {
1316         int i;
1317         char *styledirfile, *stylepath;
1318         WebKitWebContext *context;
1319         GError *error = NULL;
1320
1321         /* clean up any zombies immediately */
1322         sigchld(0);
1323         gtk_init(NULL, NULL);
1324
1325         dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
1326
1327         /* atoms */
1328         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
1329         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
1330         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
1331
1332         /* dirs and files */
1333         cookiefile = buildfile(cookiefile);
1334         scriptfile = buildfile(scriptfile);
1335         cachedir   = buildpath(cachedir);
1336         if (stylefile == NULL) {
1337                 styledir = buildpath(styledir);
1338                 for (i = 0; i < LENGTH(styles); i++) {
1339                         if (regcomp(&(styles[i].re), styles[i].regex,
1340                             REG_EXTENDED)) {
1341                                 fprintf(stderr,
1342                                         "Could not compile regex: %s\n",
1343                                         styles[i].regex);
1344                                 styles[i].regex = NULL;
1345                         }
1346                         styledirfile    = g_strconcat(styledir, "/",
1347                                                       styles[i].style, NULL);
1348                         stylepath       = buildfile(styledirfile);
1349                         styles[i].style = g_strconcat("file://", stylepath,
1350                                                       NULL);
1351                         g_free(styledirfile);
1352                         g_free(stylepath);
1353                 }
1354                 g_free(styledir);
1355         } else {
1356                 stylepath = buildfile(stylefile);
1357                 stylefile = g_strconcat("file://", stylepath, NULL);
1358                 g_free(stylepath);
1359         }
1360 }
1361
1362 void
1363 sigchld(int unused)
1364 {
1365         if (signal(SIGCHLD, sigchld) == SIG_ERR)
1366                 die("Can't install SIGCHLD handler");
1367         while (0 < waitpid(-1, NULL, WNOHANG));
1368 }
1369
1370 void
1371 spawn(Client *c, const Arg *arg)
1372 {
1373         if (fork() == 0) {
1374                 if (dpy)
1375                         close(ConnectionNumber(dpy));
1376                 setsid();
1377                 execvp(((char **)arg->v)[0], (char **)arg->v);
1378                 fprintf(stderr, "surf: execvp %s", ((char **)arg->v)[0]);
1379                 perror(" failed");
1380                 exit(0);
1381         }
1382 }
1383
1384 void
1385 eval(Client *c, const Arg *arg)
1386 {
1387         WebKitWebFrame *frame = webkit_web_view_get_main_frame(c->view);
1388         evalscript(webkit_web_frame_get_global_context(frame),
1389                    ((char **)arg->v)[0], "");
1390 }
1391
1392 void
1393 stop(Client *c, const Arg *arg)
1394 {
1395         webkit_web_view_stop_loading(c->view);
1396 }
1397
1398 void
1399 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1400 {
1401         c->title = webkit_web_view_get_title(c->view);
1402         updatetitle(c);
1403 }
1404
1405 void
1406 titlechangeleave(void *a, void *b, Client *c)
1407 {
1408         c->linkhover = NULL;
1409         updatetitle(c);
1410 }
1411
1412 void
1413 toggle(Client *c, const Arg *arg)
1414 {
1415         WebKitWebSettings *settings;
1416         char *name = (char *)arg->v;
1417         gboolean value;
1418         Arg a = { .b = FALSE };
1419
1420         settings = webkit_web_view_get_settings(c->view);
1421         g_object_get(G_OBJECT(settings), name, &value, NULL);
1422         g_object_set(G_OBJECT(settings), name, !value, NULL);
1423
1424         reload(c, &a);
1425 }
1426
1427 void
1428 togglecookiepolicy(Client *c, const Arg *arg)
1429 {
1430         ++cookiepolicy;
1431         cookiepolicy %= strlen(cookiepolicies);
1432
1433         webkit_cookie_manager_set_accept_policy(
1434             webkit_web_context_get_cookie_manager(
1435             webkit_web_view_get_context(c->view)),
1436             cookiepolicy_get());
1437
1438         updatetitle(c);
1439         /* Do not reload. */
1440 }
1441
1442 void
1443 togglegeolocation(Client *c, const Arg *arg)
1444 {
1445         Arg a = { .b = FALSE };
1446
1447         allowgeolocation ^= 1;
1448         reload(c, &a);
1449 }
1450
1451 void
1452 twitch(Client *c, const Arg *arg)
1453 {
1454         GtkAdjustment *a;
1455         gdouble v;
1456
1457         a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(
1458                                                 c->scroll));
1459
1460         v = gtk_adjustment_get_value(a);
1461
1462         v += arg->i;
1463
1464         v = MAX(v, 0.0);
1465         v = MIN(v, gtk_adjustment_get_upper(a) -
1466                 gtk_adjustment_get_page_size(a));
1467         gtk_adjustment_set_value(a, v);
1468 }
1469
1470 void
1471 togglescrollbars(Client *c, const Arg *arg)
1472 {
1473         GtkPolicyType vspolicy;
1474         Arg a;
1475
1476         gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(c->scroll), NULL,
1477                                        &vspolicy);
1478
1479         if (vspolicy == GTK_POLICY_AUTOMATIC) {
1480                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
1481                                                GTK_POLICY_NEVER,
1482                                                GTK_POLICY_NEVER);
1483         } else {
1484                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
1485                                                GTK_POLICY_AUTOMATIC,
1486                                                GTK_POLICY_AUTOMATIC);
1487                 a.i = +1;
1488                 twitch(c, &a);
1489                 a.i = -1;
1490                 twitch(c, &a);
1491         }
1492 }
1493
1494 void
1495 togglestyle(Client *c, const Arg *arg)
1496 {
1497         enablestyle = !enablestyle;
1498         setstyle(c, enablestyle ? getstyle(geturi(c)) : "");
1499
1500         updatetitle(c);
1501 }
1502
1503 void
1504 gettogglestat(Client *c)
1505 {
1506         gboolean value;
1507         int p = 0;
1508         WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
1509
1510         togglestat[p++] = cookiepolicy_set(cookiepolicy_get());
1511
1512         g_object_get(G_OBJECT(settings), "enable-caret-browsing", &value,
1513                      NULL);
1514         togglestat[p++] = value? 'C': 'c';
1515
1516         togglestat[p++] = allowgeolocation? 'G': 'g';
1517
1518         togglestat[p++] = enablediskcache? 'D': 'd';
1519
1520         g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL);
1521         togglestat[p++] = value? 'I': 'i';
1522
1523         g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL);
1524         togglestat[p++] = value? 'S': 's';
1525
1526         g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL);
1527         togglestat[p++] = value? 'V': 'v';
1528
1529         togglestat[p++] = enablestyle ? 'M': 'm';
1530
1531         togglestat[p] = '\0';
1532 }
1533
1534 void
1535 getpagestat(Client *c)
1536 {
1537         const char *uri = geturi(c);
1538
1539         pagestats[0] = c->tlsflags > G_TLS_CERTIFICATE_VALIDATE_ALL ? '-' :
1540             c->tlsflags > 0 ? 'U' : 'T';
1541         pagestat[1] = '\0';
1542 }
1543
1544 void
1545 updatetitle(Client *c)
1546 {
1547         char *t;
1548
1549         if (showindicators) {
1550                 gettogglestat(c);
1551                 getpagestat(c);
1552
1553                 if (c->linkhover) {
1554                         t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
1555                                             c->linkhover);
1556                 } else if (c->progress != 100) {
1557                         t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress,
1558                                             togglestat, pagestat,
1559                                             c->title == NULL ? "" : c->title);
1560                 } else {
1561                         t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
1562                                             c->title == NULL ? "" : c->title);
1563                 }
1564
1565                 gtk_window_set_title(GTK_WINDOW(c->win), t);
1566                 g_free(t);
1567         } else {
1568                 gtk_window_set_title(GTK_WINDOW(c->win), (c->title == NULL) ?
1569                                      "" : c->title);
1570         }
1571 }
1572
1573 void
1574 updatewinid(Client *c)
1575 {
1576         snprintf(winid, LENGTH(winid), "%lu", c->xid);
1577 }
1578
1579 void
1580 usage(void)
1581 {
1582         die("usage: %s [-bBdDfFgGiIkKmMnNpPsSvx] [-a cookiepolicies ] "
1583             "[-c cookiefile] [-e xid] [-r scriptfile] [-t stylefile] "
1584             "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
1585 }
1586
1587 void
1588 windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js,
1589                     JSObjectRef win, Client *c)
1590 {
1591         runscript(frame);
1592 }
1593
1594 void
1595 zoom(Client *c, const Arg *arg)
1596 {
1597         c->zoomed = TRUE;
1598         if (arg->i < 0) {
1599                 /* zoom out */
1600                 webkit_web_view_zoom_out(c->view);
1601         } else if (arg->i > 0) {
1602                 /* zoom in */
1603                 webkit_web_view_zoom_in(c->view);
1604         } else {
1605                 /* reset */
1606                 c->zoomed = FALSE;
1607                 webkit_web_view_set_zoom_level(c->view, 1.0);
1608         }
1609 }
1610
1611 int
1612 main(int argc, char *argv[])
1613 {
1614         Arg arg;
1615         Client *c;
1616
1617         memset(&arg, 0, sizeof(arg));
1618
1619         /* command line args */
1620         ARGBEGIN {
1621         case 'a':
1622                 cookiepolicies = EARGF(usage());
1623                 break;
1624         case 'b':
1625                 enablescrollbars = 0;
1626                 break;
1627         case 'B':
1628                 enablescrollbars = 1;
1629                 break;
1630         case 'c':
1631                 cookiefile = EARGF(usage());
1632                 break;
1633         case 'd':
1634                 enablediskcache = 0;
1635                 break;
1636         case 'D':
1637                 enablediskcache = 1;
1638                 break;
1639         case 'e':
1640                 embed = strtol(EARGF(usage()), NULL, 0);
1641                 break;
1642         case 'f':
1643                 runinfullscreen = 0;
1644                 break;
1645         case 'F':
1646                 runinfullscreen = 1;
1647                 break;
1648         case 'g':
1649                 allowgeolocation = 0;
1650                 break;
1651         case 'G':
1652                 allowgeolocation = 1;
1653                 break;
1654         case 'i':
1655                 loadimages = 0;
1656                 break;
1657         case 'I':
1658                 loadimages = 1;
1659                 break;
1660         case 'k':
1661                 kioskmode = 0;
1662                 break;
1663         case 'K':
1664                 kioskmode = 1;
1665                 break;
1666         case 'm':
1667                 enablestyle = 0;
1668                 break;
1669         case 'M':
1670                 enablestyle = 1;
1671                 break;
1672         case 'n':
1673                 enableinspector = 0;
1674                 break;
1675         case 'N':
1676                 enableinspector = 1;
1677                 break;
1678         case 'p':
1679                 enableplugins = 0;
1680                 break;
1681         case 'P':
1682                 enableplugins = 1;
1683                 break;
1684         case 'r':
1685                 scriptfile = EARGF(usage());
1686                 break;
1687         case 's':
1688                 enablescripts = 0;
1689                 break;
1690         case 'S':
1691                 enablescripts = 1;
1692                 break;
1693         case 't':
1694                 stylefile = EARGF(usage());
1695                 break;
1696         case 'u':
1697                 useragent = EARGF(usage());
1698                 break;
1699         case 'v':
1700                 die("surf-"VERSION", ©2009-2015 surf engineers, "
1701                     "see LICENSE for details\n");
1702         case 'x':
1703                 showxid = TRUE;
1704                 break;
1705         case 'z':
1706                 zoomlevel = strtof(EARGF(usage()), NULL);
1707                 break;
1708         default:
1709                 usage();
1710         } ARGEND;
1711         if (argc > 0)
1712                 arg.v = argv[0];
1713
1714         setup();
1715         c = newclient(NULL);
1716         showview(NULL, c);
1717         if (arg.v)
1718                 loaduri(clients, &arg);
1719         else
1720                 updatetitle(c);
1721
1722         gtk_main();
1723         cleanup();
1724
1725         return EXIT_SUCCESS;
1726 }
1727