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