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