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