Adapt scrolling
[surf.git] / surf.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * To understand surf, start reading main().
4  */
5 #include <signal.h>
6 #include <X11/X.h>
7 #include <X11/Xatom.h>
8 #include <gtk/gtkx.h>
9 #include <gtk/gtk.h>
10 #include <gdk/gdkx.h>
11 #include <gdk/gdk.h>
12 #include <gdk/gdkkeysyms.h>
13 #include <string.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17 #include <limits.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <webkit2/webkit2.h>
21 #include <glib/gstdio.h>
22 #include <JavaScriptCore/JavaScript.h>
23 #include <sys/file.h>
24 #include <libgen.h>
25 #include <stdarg.h>
26 #include <regex.h>
27 #include <pwd.h>
28 #include <string.h>
29
30 #include "arg.h"
31
32 char *argv0;
33
34 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
35 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
36
37 enum { AtomFind, AtomGo, AtomUri, AtomLast };
38
39 enum {
40         OnDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
41         OnLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
42         OnImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
43         OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
44         OnEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
45         OnBar   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
46         OnSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
47         OnAny   = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
48 };
49
50 typedef union Arg Arg;
51 union Arg {
52         gboolean b;
53         gint i;
54         const void *v;
55 };
56
57 typedef struct Client {
58         GtkWidget *win;
59         Window xid;
60         WebKitWebView *view;
61         WebKitWebInspector *inspector;
62         WebKitHitTestResult *mousepos;
63         GTlsCertificateFlags tlsflags;
64         const char *title, *targeturi;
65         const char *needle;
66         gint progress;
67         struct Client *next;
68         gboolean zoomed, fullscreen;
69 } Client;
70
71 typedef struct {
72         guint mod;
73         guint keyval;
74         void (*func)(Client *c, const Arg *arg);
75         const Arg arg;
76 } Key;
77
78 typedef struct {
79         unsigned int target;
80         unsigned int mask;
81         guint button;
82         void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
83         const Arg arg;
84         unsigned int stopevent;
85 } Button;
86
87 typedef struct {
88         char *regex;
89         char *style;
90         regex_t re;
91 } SiteStyle;
92
93 static Display *dpy;
94 static Atom atoms[AtomLast];
95 static Client *clients = NULL;
96 static Window embed = 0;
97 static gboolean showxid = FALSE;
98 static char winid[64];
99 static char togglestat[9];
100 static char pagestat[3];
101 static GTlsDatabase *tlsdb;
102 static int cookiepolicy;
103 static char *stylefile = NULL;
104
105 static void addaccelgroup(Client *c);
106 static char *buildfile(const char *path);
107 static char *buildpath(const char *path);
108 static gboolean buttonreleased(GtkWidget *w, GdkEventKey *e, Client *c);
109 static void cleanup(void);
110 static void clipboard(Client *c, const Arg *a);
111
112 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
113 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
114
115 static char *copystr(char **str, const char *src);
116 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
117                 Client *c);
118 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
119     WebKitPolicyDecisionType dt, Client *c);
120 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
121 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
122 static void decideresource(WebKitPolicyDecision *d, Client *c);
123 static void closeview(WebKitWebView *v, Client *c);
124 static void destroyclient(Client *c);
125 static void destroywin(GtkWidget* w, Client *c);
126 static void die(const char *errstr, ...);
127 static void evalscript(Client *c, const char *jsstr, ...);
128 static void runscript(Client *c);
129 static void find(Client *c, const Arg *arg);
130 static void togglefullscreen(Client *c, const Arg *a);
131 static gboolean permissionrequested(WebKitWebView *v,
132                 WebKitPermissionRequest *r, Client *c);
133 static const char *getatom(Client *c, int a);
134 static void gettogglestat(Client *c);
135 static void getpagestat(Client *c);
136 static char *geturi(Client *c);
137 static const gchar *getstyle(const char *uri);
138 static void setstyle(Client *c, const char *stylefile);
139
140 static void handleplumb(Client *c, const gchar *uri);
141
142 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
143                 Client *c);
144 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
145 static void download(Client *c, WebKitURIResponse *r);
146
147 static void toggleinspector(Client *c, const Arg *a);
148
149 static gboolean keypress(GtkAccelGroup *group, GObject *obj, guint key,
150                          GdkModifierType mods, Client *c);
151 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
152                 guint modifiers, Client *c);
153 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
154 static void loaduri(Client *c, const Arg *arg);
155 static void navigate(Client *c, const Arg *arg);
156 static Client *newclient(Client *c);
157 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
158 static void showview(WebKitWebView *v, Client *c);
159 static void newwindow(Client *c, const Arg *arg, gboolean noembed);
160 static GtkWidget *createwindow(Client *c);
161 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
162 static void print(Client *c, const Arg *a);
163 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
164                                 gpointer d);
165 static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
166 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
167 static void linkopen(Client *c, const Arg *arg);
168 static void linkopenembed(Client *c, const Arg *arg);
169 static void reload(Client *c, const Arg *arg);
170 static void scroll_h(Client *c, const Arg *a);
171 static void scroll_v(Client *c, const Arg *a);
172 static void setatom(Client *c, int a, const char *v);
173 static void setup(void);
174 static void sigchld(int unused);
175 static void spawn(Client *c, const Arg *arg);
176 static void stop(Client *c, const Arg *arg);
177 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
178 static void toggle(Client *c, const Arg *arg);
179 static void togglecookiepolicy(Client *c, const Arg *arg);
180 static void togglegeolocation(Client *c, const Arg *arg);
181 static void togglescrollbars(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 *arg);
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 *arg)
586 {
587         const char *s;
588
589         s = getatom(c, AtomFind);
590         gboolean forward = *(gboolean *)arg;
591         webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE);
592 }
593
594 void
595 togglefullscreen(Client *c, const Arg *a)
596 {
597         /* toggling value is handled in winevent() */
598         if (c->fullscreen)
599                 gtk_window_unfullscreen(GTK_WINDOW(c->win));
600         else
601                 gtk_window_fullscreen(GTK_WINDOW(c->win));
602 }
603
604 gboolean
605 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
606 {
607         if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
608                 if (allowgeolocation)
609                         webkit_permission_request_allow(r);
610                 else
611                         webkit_permission_request_deny(r);
612                 return TRUE;
613         }
614
615         return FALSE;
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, c->xid,
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 *stylefile)
668 {
669         gchar *style;
670
671         if (!g_file_get_contents(stylefile, &style, NULL, NULL)) {
672                 fprintf(stderr, "Could not read style file: %s\n", stylefile);
673                 return;
674         }
675
676         webkit_user_content_manager_add_style_sheet(
677             webkit_web_view_get_user_content_manager(c->view),
678             webkit_user_style_sheet_new(style,
679             WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
680             WEBKIT_USER_STYLE_LEVEL_USER,
681             NULL, NULL));
682
683         g_free(style);
684 }
685
686 void
687 handleplumb(Client *c, const gchar *uri)
688 {
689         Arg arg;
690
691         arg = (Arg)PLUMB(uri);
692         spawn(c, &arg);
693 }
694
695 void
696 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
697 {
698         g_signal_connect(G_OBJECT(d), "notify::response",
699             G_CALLBACK(responsereceived), c);
700 }
701
702 void
703 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
704 {
705         download(c, webkit_download_get_response(d));
706         webkit_download_cancel(d);
707 }
708
709 void
710 download(Client *c, WebKitURIResponse *r)
711 {
712         Arg a;
713
714         a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
715         spawn(c, &a);
716 }
717
718 void
719 toggleinspector(Client *c, const Arg *a)
720 {
721         if (enableinspector) {
722                 if (webkit_web_inspector_is_attached(c->inspector))
723                         webkit_web_inspector_close(c->inspector);
724                 else
725                         webkit_web_inspector_show(c->inspector);
726         }
727 }
728
729 gboolean
730 keypress(GtkAccelGroup *group, GObject *obj, guint key, GdkModifierType mods,
731          Client *c)
732 {
733         guint i;
734         gboolean processed = FALSE;
735
736         mods = CLEANMASK(mods);
737         key = gdk_keyval_to_lower(key);
738         updatewinid(c);
739         for (i = 0; i < LENGTH(keys); i++) {
740                 if (key == keys[i].keyval
741                     && mods == keys[i].mod
742                     && keys[i].func) {
743                         keys[i].func(c, &(keys[i].arg));
744                         processed = TRUE;
745                 }
746         }
747
748         return processed;
749 }
750
751 void
752 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
753     Client *c)
754 {
755         WebKitHitTestResultContext hc;
756
757         /* Keep the hit test to know where is the pointer on the next click */
758         c->mousepos = h;
759
760         hc = webkit_hit_test_result_get_context(h);
761
762         if (hc & OnLink)
763                 c->targeturi = webkit_hit_test_result_get_link_uri(h);
764         else if (hc & OnImg)
765                 c->targeturi = webkit_hit_test_result_get_image_uri(h);
766         else if (hc & OnMedia)
767                 c->targeturi = webkit_hit_test_result_get_media_uri(h);
768         else
769                 c->targeturi = NULL;
770         updatetitle(c);
771 }
772
773 void
774 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
775 {
776         switch (e) {
777         case WEBKIT_LOAD_STARTED:
778                 c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
779                 break;
780         case WEBKIT_LOAD_REDIRECTED:
781                 setatom(c, AtomUri, geturi(c));
782                 break;
783         case WEBKIT_LOAD_COMMITTED:
784                 if (!webkit_web_view_get_tls_info(c->view, NULL, &(c->tlsflags)))
785                         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
786
787                 setatom(c, AtomUri, geturi(c));
788
789                 if (enablestyle)
790                         setstyle(c, getstyle(geturi(c)));
791                 break;
792         case WEBKIT_LOAD_FINISHED:
793                 /* Disabled until we write some WebKitWebExtension for
794                  * manipulating the DOM directly.
795                 evalscript(c, "document.documentElement.style.overflow = '%s'",
796                     enablescrollbars ? "auto" : "hidden");
797                 */
798                 runscript(c);
799                 break;
800         }
801         updatetitle(c);
802 }
803
804 void
805 loaduri(Client *c, const Arg *arg)
806 {
807         char *u = NULL, *rp;
808         const char *uri = (char *)arg->v;
809         Arg a = { .b = FALSE };
810         struct stat st;
811
812         if (strcmp(uri, "") == 0)
813                 return;
814
815         /* In case it's a file path. */
816         if (stat(uri, &st) == 0) {
817                 rp = realpath(uri, NULL);
818                 u = g_strdup_printf("file://%s", rp);
819                 free(rp);
820         } else {
821                 u = g_strrstr(uri, "://") ? g_strdup(uri)
822                     : g_strdup_printf("http://%s", uri);
823         }
824
825         setatom(c, AtomUri, uri);
826
827         /* prevents endless loop */
828         if (strcmp(u, geturi(c)) == 0) {
829                 reload(c, &a);
830         } else {
831                 webkit_web_view_load_uri(c->view, u);
832                 c->progress = 0;
833                 c->title = copystr(&c->title, u);
834                 updatetitle(c);
835         }
836         g_free(u);
837 }
838
839 void
840 navigate(Client *c, const Arg *arg)
841 {
842         int steps = *(int *)arg;
843         webkit_web_view_go_back_or_forward(c->view, steps);
844 }
845
846 Client *
847 newclient(Client *rc)
848 {
849         Client *c;
850         gdouble dpi;
851
852         if (!(c = calloc(1, sizeof(Client))))
853                 die("Cannot malloc!\n");
854
855         c->title = NULL;
856         c->progress = 100;
857
858         c->next = clients;
859         clients = c;
860
861         c->view = newview(c, rc ? rc->view : NULL);
862         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
863
864         return c;
865 }
866
867 WebKitWebView *
868 newview(Client *c, WebKitWebView *rv)
869 {
870         WebKitWebView *v;
871         WebKitSettings *settings;
872         WebKitUserContentManager *contentmanager;
873         WebKitWebContext *context;
874         char *ua;
875
876         /* Webview */
877         if (rv) {
878                 v = WEBKIT_WEB_VIEW(
879                     webkit_web_view_new_with_related_view(rv));
880         } else {
881                 settings = webkit_settings_new_with_settings(
882                     "auto-load-images", loadimages,
883                     "default-font-size", defaultfontsize,
884                     "enable-caret-browsing", enablecaretbrowsing,
885                     "enable-developer-extras", enableinspector,
886                     "enable-dns-prefetching", enablednsprefetching,
887                     "enable-frame-flattening", enableframeflattening,
888                     "enable-html5-database", enablecache,
889                     "enable-html5-local-storage", enablecache,
890                     "enable-javascript", enablescripts,
891                     "enable-plugins", enableplugins,
892                     NULL);
893                 if (!(ua = getenv("SURF_USERAGENT")))
894                         ua = useragent;
895                 webkit_settings_set_user_agent(settings, ua);
896                 /* Have a look at http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html
897                  * for more interesting settings */
898
899                 contentmanager = webkit_user_content_manager_new();
900
901                 context = webkit_web_context_new_with_website_data_manager(
902                     webkit_website_data_manager_new(
903                     "base-cache-directory", cachedir,
904                     "base-data-directory", cachedir,
905                     NULL));
906
907                 /* rendering process model, can be a shared unique one or one for each
908                  * view */
909                 webkit_web_context_set_process_model(context,
910                     WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
911                 /* ssl */
912                 webkit_web_context_set_tls_errors_policy(context, strictssl ?
913                     WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
914                 /* disk cache */
915                 webkit_web_context_set_cache_model(context, enablecache ?
916                     WEBKIT_CACHE_MODEL_WEB_BROWSER : WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
917
918                 /* Currently only works with text file to be compatible with curl */
919                 webkit_cookie_manager_set_persistent_storage(
920                     webkit_web_context_get_cookie_manager(context), cookiefile,
921                     WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
922                 /* cookie policy */
923                 webkit_cookie_manager_set_accept_policy(
924                     webkit_web_context_get_cookie_manager(context),
925                     cookiepolicy_get());
926
927                 g_signal_connect(G_OBJECT(context), "download-started",
928                     G_CALLBACK(downloadstarted), c);
929
930                 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
931                     "settings", settings,
932                     "user-content-manager", contentmanager,
933                     "web-context", context,
934                     NULL);
935         }
936
937         g_signal_connect(G_OBJECT(v),
938                          "notify::title",
939                          G_CALLBACK(titlechanged), c);
940         g_signal_connect(G_OBJECT(v),
941                          "mouse-target-changed",
942                          G_CALLBACK(mousetargetchanged), c);
943         g_signal_connect(G_OBJECT(v),
944                          "permission-request",
945                          G_CALLBACK(permissionrequested), c);
946         g_signal_connect(G_OBJECT(v),
947                          "create",
948                          G_CALLBACK(createview), c);
949         g_signal_connect(G_OBJECT(v), "ready-to-show",
950                          G_CALLBACK(showview), c);
951         g_signal_connect(G_OBJECT(v),
952                          "decide-policy",
953                          G_CALLBACK(decidepolicy), c);
954         g_signal_connect(G_OBJECT(v),
955                          "load-changed",
956                          G_CALLBACK(loadchanged), c);
957         g_signal_connect(G_OBJECT(v),
958                          "notify::estimated-load-progress",
959                          G_CALLBACK(progresschanged), c);
960         g_signal_connect(G_OBJECT(v),
961                          "button-release-event",
962                          G_CALLBACK(buttonreleased), c);
963         g_signal_connect(G_OBJECT(v), "close",
964                         G_CALLBACK(closeview), c);
965
966         return v;
967 }
968
969 void
970 showview(WebKitWebView *v, Client *c)
971 {
972         GdkGeometry hints = { 1, 1 };
973         GdkRGBA bgcolor = { 0 };
974         GdkWindow *gwin;
975
976         c->win = createwindow(c);
977
978         if (enableinspector)
979                 c->inspector = webkit_web_view_get_inspector(c->view);
980
981         if (!kioskmode)
982                 addaccelgroup(c);
983
984         /* Arranging */
985         gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
986
987         /* Setup */
988         gtk_widget_grab_focus(GTK_WIDGET(c->view));
989         gtk_widget_show(GTK_WIDGET(c->view));
990         gtk_widget_show(c->win);
991         gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
992         c->xid = gdk_x11_window_get_xid(gwin);
993         gtk_window_set_geometry_hints(GTK_WINDOW(c->win), NULL, &hints,
994                                       GDK_HINT_MIN_SIZE);
995         gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
996         gdk_window_add_filter(gwin, processx, c);
997
998         /* This might conflict with _zoomto96dpi_. */
999         if (zoomlevel != 1.0)
1000                 webkit_web_view_set_zoom_level(c->view, zoomlevel);
1001
1002         if (runinfullscreen)
1003                 togglefullscreen(c, NULL);
1004
1005         setatom(c, AtomFind, "");
1006         setatom(c, AtomUri, "about:blank");
1007         if (hidebackground)
1008                 webkit_web_view_set_background_color(c->view, &bgcolor);
1009
1010         if (showxid) {
1011                 gdk_display_sync(gtk_widget_get_display(c->win));
1012                 printf("%lu\n", c->xid);
1013                 fflush(NULL);
1014                 if (fclose(stdout) != 0) {
1015                         die("Error closing stdout");
1016                 }
1017         }
1018 }
1019
1020 void
1021 newwindow(Client *c, const Arg *arg, gboolean noembed)
1022 {
1023         guint i = 0;
1024         const char *cmd[18], *uri;
1025         const Arg a = { .v = (void *)cmd };
1026         char tmp[64];
1027
1028         cmd[i++] = argv0;
1029         cmd[i++] = "-a";
1030         cmd[i++] = cookiepolicies;
1031         if (!enablescrollbars)
1032                 cmd[i++] = "-b";
1033         if (embed && !noembed) {
1034                 cmd[i++] = "-e";
1035                 snprintf(tmp, LENGTH(tmp), "%u", (int)embed);
1036                 cmd[i++] = tmp;
1037         }
1038         if (!allowgeolocation)
1039                 cmd[i++] = "-g";
1040         if (!loadimages)
1041                 cmd[i++] = "-i";
1042         if (kioskmode)
1043                 cmd[i++] = "-k";
1044         if (!enableplugins)
1045                 cmd[i++] = "-p";
1046         if (!enablescripts)
1047                 cmd[i++] = "-s";
1048         if (showxid)
1049                 cmd[i++] = "-x";
1050         if (enablediskcache)
1051                 cmd[i++] = "-D";
1052         cmd[i++] = "-c";
1053         cmd[i++] = cookiefile;
1054         cmd[i++] = "--";
1055         uri = arg->v ? (char *)arg->v : c->linkhover;
1056         if (uri)
1057                 cmd[i++] = uri;
1058         cmd[i++] = NULL;
1059         spawn(NULL, &a);
1060 }
1061
1062 GtkWidget *
1063 createwindow(Client *c)
1064 {
1065         GtkWidget *w;
1066
1067         if (embed) {
1068                 w = gtk_plug_new(embed);
1069         } else {
1070                 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1071
1072                 /* TA:  20091214:  Despite what the GNOME docs say, the ICCCM
1073                  * is always correct, so we should still call this function.
1074                  * But when doing so, we *must* differentiate between a
1075                  * WM_CLASS and a resource on the window.  By convention, the
1076                  * window class (WM_CLASS) is capped, while the resource is in
1077                  * lowercase.   Both these values come as a pair.
1078                  */
1079                 gtk_window_set_wmclass(GTK_WINDOW(w), "surf", "Surf");
1080
1081                 /* TA:  20091214:  And set the role here as well -- so that
1082                  * sessions can pick this up.
1083                  */
1084                 gtk_window_set_role(GTK_WINDOW(w), "Surf");
1085
1086                 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
1087         }
1088
1089         g_signal_connect(G_OBJECT(w), "destroy",
1090             G_CALLBACK(destroywin), c);
1091         g_signal_connect(G_OBJECT(w), "leave-notify-event",
1092             G_CALLBACK(winevent), c);
1093         g_signal_connect(G_OBJECT(w), "window-state-event",
1094             G_CALLBACK(winevent), c);
1095
1096         return w;
1097 }
1098
1099 void
1100 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1101 {
1102         Arg arg = {.v = text };
1103         if (text != NULL)
1104                 loaduri((Client *) d, &arg);
1105 }
1106
1107 void
1108 print(Client *c, const Arg *a)
1109 {
1110         webkit_print_operation_run_dialog(webkit_print_operation_new(c->view),
1111             GTK_WINDOW(c->win));
1112 }
1113
1114 GdkFilterReturn
1115 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
1116 {
1117         Client *c = (Client *)d;
1118         XPropertyEvent *ev;
1119         Arg arg;
1120
1121         if (((XEvent *)e)->type == PropertyNotify) {
1122                 ev = &((XEvent *)e)->xproperty;
1123                 if (ev->state == PropertyNewValue) {
1124                         if (ev->atom == atoms[AtomFind]) {
1125                                 arg.b = TRUE;
1126                                 find(c, &arg);
1127
1128                                 return GDK_FILTER_REMOVE;
1129                         } else if (ev->atom == atoms[AtomGo]) {
1130                                 arg.v = getatom(c, AtomGo);
1131                                 loaduri(c, &arg);
1132
1133                                 return GDK_FILTER_REMOVE;
1134                         }
1135                 }
1136         }
1137         return GDK_FILTER_CONTINUE;
1138 }
1139
1140 void
1141 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1142 {
1143         c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
1144             100;
1145         updatetitle(c);
1146 }
1147
1148 void
1149 linkopen(Client *c, const Arg *arg)
1150 {
1151         newwindow(NULL, arg, 1);
1152 }
1153
1154 void
1155 linkopenembed(Client *c, const Arg *arg)
1156 {
1157         newwindow(NULL, arg, 0);
1158 }
1159
1160 void
1161 reload(Client *c, const Arg *arg)
1162 {
1163         gboolean nocache = *(gboolean *)arg;
1164         if (nocache)
1165                 webkit_web_view_reload_bypass_cache(c->view);
1166         else
1167                 webkit_web_view_reload(c->view);
1168 }
1169
1170 void
1171 scroll_h(Client *c, const Arg *a)
1172 {
1173         evalscript(c, "window.scrollBy(%d * (window.innerWidth / 100), 0)",
1174             a->i);
1175 }
1176
1177 void
1178 scroll_v(Client *c, const Arg *a)
1179 {
1180         evalscript(c, "window.scrollBy(0, %d * (window.innerHeight / 100))",
1181             a->i);
1182 }
1183
1184 void
1185 setatom(Client *c, int a, const char *v)
1186 {
1187         XSync(dpy, False);
1188         XChangeProperty(dpy, c->xid,
1189                         atoms[a], XA_STRING, 8, PropModeReplace,
1190                         (unsigned char *)v, strlen(v) + 1);
1191 }
1192
1193 void
1194 setup(void)
1195 {
1196         int i;
1197         WebKitWebContext *context;
1198         GError *error = NULL;
1199
1200         /* clean up any zombies immediately */
1201         sigchld(0);
1202         gtk_init(NULL, NULL);
1203
1204         dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
1205
1206         /* atoms */
1207         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
1208         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
1209         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
1210
1211         /* dirs and files */
1212         cookiefile = buildfile(cookiefile);
1213         scriptfile = buildfile(scriptfile);
1214         cachedir   = buildpath(cachedir);
1215
1216         if (stylefile == NULL) {
1217                 styledir = buildpath(styledir);
1218                 for (i = 0; i < LENGTH(styles); i++) {
1219                         if (regcomp(&(styles[i].re), styles[i].regex,
1220                             REG_EXTENDED)) {
1221                                 fprintf(stderr,
1222                                         "Could not compile regex: %s\n",
1223                                         styles[i].regex);
1224                                 styles[i].regex = NULL;
1225                         }
1226                         styles[i].style = g_strconcat(styledir, "/",
1227                             styles[i].style, NULL);
1228                 }
1229                 g_free(styledir);
1230         } else {
1231                 stylefile = buildfile(stylefile);
1232         }
1233 }
1234
1235 void
1236 sigchld(int unused)
1237 {
1238         if (signal(SIGCHLD, sigchld) == SIG_ERR)
1239                 die("Can't install SIGCHLD handler");
1240         while (0 < waitpid(-1, NULL, WNOHANG));
1241 }
1242
1243 void
1244 spawn(Client *c, const Arg *arg)
1245 {
1246         if (fork() == 0) {
1247                 if (dpy)
1248                         close(ConnectionNumber(dpy));
1249                 setsid();
1250                 execvp(((char **)arg->v)[0], (char **)arg->v);
1251                 fprintf(stderr, "surf: execvp %s", ((char **)arg->v)[0]);
1252                 perror(" failed");
1253                 exit(0);
1254         }
1255 }
1256
1257 void
1258 stop(Client *c, const Arg *arg)
1259 {
1260         webkit_web_view_stop_loading(c->view);
1261 }
1262
1263 void
1264 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1265 {
1266         c->title = webkit_web_view_get_title(c->view);
1267         updatetitle(c);
1268 }
1269
1270 gboolean
1271 winevent(GtkWidget *w, GdkEvent *e, Client *c)
1272 {
1273         switch (e->type) {
1274         case GDK_LEAVE_NOTIFY:
1275                 c->targeturi = NULL;
1276                 updatetitle(c);
1277                 break;
1278         case GDK_WINDOW_STATE: /* fallthrough */
1279                 if (e->window_state.changed_mask ==
1280                     GDK_WINDOW_STATE_FULLSCREEN) {
1281                         c->fullscreen = e->window_state.new_window_state &
1282                             GDK_WINDOW_STATE_FULLSCREEN;
1283                         break;
1284                 }
1285         default:
1286                 return FALSE;
1287         }
1288
1289         return TRUE;
1290 }
1291
1292 void
1293 toggle(Client *c, const Arg *arg)
1294 {
1295         WebKitWebSettings *settings;
1296         char *name = (char *)arg->v;
1297         gboolean value;
1298         Arg a = { .b = FALSE };
1299
1300         settings = webkit_web_view_get_settings(c->view);
1301         g_object_get(G_OBJECT(settings), name, &value, NULL);
1302         g_object_set(G_OBJECT(settings), name, !value, NULL);
1303
1304         reload(c, &a);
1305 }
1306
1307 void
1308 togglecookiepolicy(Client *c, const Arg *arg)
1309 {
1310         ++cookiepolicy;
1311         cookiepolicy %= strlen(cookiepolicies);
1312
1313         webkit_cookie_manager_set_accept_policy(
1314             webkit_web_context_get_cookie_manager(
1315             webkit_web_view_get_context(c->view)),
1316             cookiepolicy_get());
1317
1318         updatetitle(c);
1319         /* Do not reload. */
1320 }
1321
1322 void
1323 togglegeolocation(Client *c, const Arg *arg)
1324 {
1325         Arg a = { .b = FALSE };
1326
1327         allowgeolocation ^= 1;
1328         reload(c, &a);
1329 }
1330
1331 void
1332 twitch(Client *c, const Arg *arg)
1333 {
1334         GtkAdjustment *a;
1335         gdouble v;
1336
1337         a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(
1338                                                 c->scroll));
1339
1340         v = gtk_adjustment_get_value(a);
1341
1342         v += arg->i;
1343
1344         v = MAX(v, 0.0);
1345         v = MIN(v, gtk_adjustment_get_upper(a) -
1346                 gtk_adjustment_get_page_size(a));
1347         gtk_adjustment_set_value(a, v);
1348 }
1349
1350 void
1351 togglescrollbars(Client *c, const Arg *arg)
1352 {
1353         GtkPolicyType vspolicy;
1354         Arg a;
1355
1356         gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(c->scroll), NULL,
1357                                        &vspolicy);
1358
1359         if (vspolicy == GTK_POLICY_AUTOMATIC) {
1360                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
1361                                                GTK_POLICY_NEVER,
1362                                                GTK_POLICY_NEVER);
1363         } else {
1364                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
1365                                                GTK_POLICY_AUTOMATIC,
1366                                                GTK_POLICY_AUTOMATIC);
1367                 a.i = +1;
1368                 twitch(c, &a);
1369                 a.i = -1;
1370                 twitch(c, &a);
1371         }
1372 }
1373
1374 void
1375 togglestyle(Client *c, const Arg *arg)
1376 {
1377         enablestyle = !enablestyle;
1378         setstyle(c, enablestyle ? getstyle(geturi(c)) : "");
1379
1380         updatetitle(c);
1381 }
1382
1383 void
1384 gettogglestat(Client *c)
1385 {
1386         gboolean value;
1387         int p = 0;
1388         WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
1389
1390         togglestat[p++] = cookiepolicy_set(cookiepolicy_get());
1391
1392         g_object_get(G_OBJECT(settings), "enable-caret-browsing", &value,
1393                      NULL);
1394         togglestat[p++] = value? 'C': 'c';
1395
1396         togglestat[p++] = allowgeolocation? 'G': 'g';
1397
1398         togglestat[p++] = enablediskcache? 'D': 'd';
1399
1400         g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL);
1401         togglestat[p++] = value? 'I': 'i';
1402
1403         g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL);
1404         togglestat[p++] = value? 'S': 's';
1405
1406         g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL);
1407         togglestat[p++] = value? 'V': 'v';
1408
1409         togglestat[p++] = enablestyle ? 'M': 'm';
1410
1411         togglestat[p] = '\0';
1412 }
1413
1414 void
1415 getpagestat(Client *c)
1416 {
1417         const char *uri = geturi(c);
1418
1419         pagestats[0] = c->tlsflags > G_TLS_CERTIFICATE_VALIDATE_ALL ? '-' :
1420             c->tlsflags > 0 ? 'U' : 'T';
1421         pagestat[1] = '\0';
1422 }
1423
1424 void
1425 updatetitle(Client *c)
1426 {
1427         char *t;
1428
1429         if (showindicators) {
1430                 gettogglestat(c);
1431                 getpagestat(c);
1432
1433                 if (c->linkhover) {
1434                         t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
1435                                             c->linkhover);
1436                 } else if (c->progress != 100) {
1437                         t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress,
1438                                             togglestat, pagestat,
1439                                             c->title == NULL ? "" : c->title);
1440                 } else {
1441                         t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
1442                                             c->title == NULL ? "" : c->title);
1443                 }
1444
1445                 gtk_window_set_title(GTK_WINDOW(c->win), t);
1446                 g_free(t);
1447         } else {
1448                 gtk_window_set_title(GTK_WINDOW(c->win), (c->title == NULL) ?
1449                                      "" : c->title);
1450         }
1451 }
1452
1453 void
1454 updatewinid(Client *c)
1455 {
1456         snprintf(winid, LENGTH(winid), "%lu", c->xid);
1457 }
1458
1459 void
1460 usage(void)
1461 {
1462         die("usage: %s [-bBdDfFgGiIkKmMnNpPsSvx] [-a cookiepolicies ] "
1463             "[-c cookiefile] [-e xid] [-r scriptfile] [-t stylefile] "
1464             "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
1465 }
1466
1467 void
1468 zoom(Client *c, const Arg *arg)
1469 {
1470         c->zoomed = TRUE;
1471         if (arg->i < 0) {
1472                 /* zoom out */
1473                 webkit_web_view_zoom_out(c->view);
1474         } else if (arg->i > 0) {
1475                 /* zoom in */
1476                 webkit_web_view_zoom_in(c->view);
1477         } else {
1478                 /* reset */
1479                 c->zoomed = FALSE;
1480                 webkit_web_view_set_zoom_level(c->view, 1.0);
1481         }
1482 }
1483
1484 int
1485 main(int argc, char *argv[])
1486 {
1487         Arg arg;
1488         Client *c;
1489
1490         memset(&arg, 0, sizeof(arg));
1491
1492         /* command line args */
1493         ARGBEGIN {
1494         case 'a':
1495                 cookiepolicies = EARGF(usage());
1496                 break;
1497         case 'b':
1498                 enablescrollbars = 0;
1499                 break;
1500         case 'B':
1501                 enablescrollbars = 1;
1502                 break;
1503         case 'c':
1504                 cookiefile = EARGF(usage());
1505                 break;
1506         case 'd':
1507                 enablediskcache = 0;
1508                 break;
1509         case 'D':
1510                 enablediskcache = 1;
1511                 break;
1512         case 'e':
1513                 embed = strtol(EARGF(usage()), NULL, 0);
1514                 break;
1515         case 'f':
1516                 runinfullscreen = 0;
1517                 break;
1518         case 'F':
1519                 runinfullscreen = 1;
1520                 break;
1521         case 'g':
1522                 allowgeolocation = 0;
1523                 break;
1524         case 'G':
1525                 allowgeolocation = 1;
1526                 break;
1527         case 'i':
1528                 loadimages = 0;
1529                 break;
1530         case 'I':
1531                 loadimages = 1;
1532                 break;
1533         case 'k':
1534                 kioskmode = 0;
1535                 break;
1536         case 'K':
1537                 kioskmode = 1;
1538                 break;
1539         case 'm':
1540                 enablestyle = 0;
1541                 break;
1542         case 'M':
1543                 enablestyle = 1;
1544                 break;
1545         case 'n':
1546                 enableinspector = 0;
1547                 break;
1548         case 'N':
1549                 enableinspector = 1;
1550                 break;
1551         case 'p':
1552                 enableplugins = 0;
1553                 break;
1554         case 'P':
1555                 enableplugins = 1;
1556                 break;
1557         case 'r':
1558                 scriptfile = EARGF(usage());
1559                 break;
1560         case 's':
1561                 enablescripts = 0;
1562                 break;
1563         case 'S':
1564                 enablescripts = 1;
1565                 break;
1566         case 't':
1567                 stylefile = EARGF(usage());
1568                 break;
1569         case 'u':
1570                 useragent = EARGF(usage());
1571                 break;
1572         case 'v':
1573                 die("surf-"VERSION", ©2009-2015 surf engineers, "
1574                     "see LICENSE for details\n");
1575         case 'x':
1576                 showxid = TRUE;
1577                 break;
1578         case 'z':
1579                 zoomlevel = strtof(EARGF(usage()), NULL);
1580                 break;
1581         default:
1582                 usage();
1583         } ARGEND;
1584         if (argc > 0)
1585                 arg.v = argv[0];
1586
1587         setup();
1588         c = newclient(NULL);
1589         showview(NULL, c);
1590         if (arg.v)
1591                 loaduri(clients, &arg);
1592         else
1593                 updatetitle(c);
1594
1595         gtk_main();
1596         cleanup();
1597
1598         return EXIT_SUCCESS;
1599 }
1600