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