Fix some styling in the config.def.h, fix the DOWNLOAD macro and unify the
[surf.git] / surf.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * To understand surf, start reading main().
4  */
5 #include <signal.h>
6 #include <X11/X.h>
7 #include <X11/Xatom.h>
8 #include <gtk/gtk.h>
9 #include <gdk/gdkx.h>
10 #include <gdk/gdk.h>
11 #include <gdk/gdkkeysyms.h>
12 #include <string.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <webkit/webkit.h>
19 #include <glib/gstdio.h>
20 #include <JavaScriptCore/JavaScript.h>
21 #include <sys/file.h>
22
23 #include "arg.h"
24
25 char *argv0;
26
27 #define LENGTH(x)               (sizeof x / sizeof x[0])
28 #define COOKIEJAR_TYPE          (cookiejar_get_type ())
29 #define COOKIEJAR(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar))
30
31 enum { AtomFind, AtomGo, AtomUri, AtomLast };
32
33 typedef union Arg Arg;
34 union Arg {
35         gboolean b;
36         gint i;
37         const void *v;
38 };
39
40 typedef struct Client {
41         GtkWidget *win, *scroll, *vbox, *indicator;
42         WebKitWebView *view;
43         char *title, *linkhover;
44         const char *uri, *needle;
45         gint progress;
46         gboolean sslfailed;
47         struct Client *next;
48         gboolean zoomed;
49 } Client;
50
51 typedef struct {
52         char *label;
53         void (*func)(Client *c, const Arg *arg);
54         const Arg arg;
55 } Item;
56
57 typedef struct {
58         guint mod;
59         guint keyval;
60         void (*func)(Client *c, const Arg *arg);
61         const Arg arg;
62 } Key;
63
64 typedef struct {
65         SoupCookieJarText parent_instance;
66         int lock;
67 } CookieJar;
68
69 typedef struct {
70         SoupCookieJarTextClass parent_class;
71 } CookieJarClass;
72
73 G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT)
74
75 static Display *dpy;
76 static Atom atoms[AtomLast];
77 static Client *clients = NULL;
78 static GdkNativeWindow embed = 0;
79 static gboolean showxid = FALSE;
80 static char winid[64];
81 static gboolean loadimages = 1, enableplugins = 1, enablescripts = 1,
82                 usingproxy = 0;
83 static char togglestat[5];
84
85 static char *buildpath(const char *path);
86 static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e, GList *gl);
87 static void cleanup(void);
88 static void clipboard(Client *c, const Arg *arg);
89 static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, SoupCookie *new_cookie);
90 static void cookiejar_finalize(GObject *self);
91 static SoupCookieJar *cookiejar_new(const char *filename, gboolean read_only);
92 static void cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec);
93 static char *copystr(char **str, const char *src);
94 static WebKitWebView *createwindow(WebKitWebView *v, WebKitWebFrame *f, Client *c);
95 static gboolean decidedownload(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r, gchar *m,  WebKitWebPolicyDecision *p, Client *c);
96 static gboolean decidewindow(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r, WebKitWebNavigationAction *n, WebKitWebPolicyDecision *p, Client *c);
97 static void destroyclient(Client *c);
98 static void destroywin(GtkWidget* w, Client *c);
99 static void die(char *str);
100 static void drawindicator(Client *c);
101 static gboolean exposeindicator(GtkWidget *w, GdkEventExpose *e, Client *c);
102 static void find(Client *c, const Arg *arg);
103 static const char *getatom(Client *c, int a);
104 static char *geturi(Client *c);
105 static gboolean initdownload(WebKitWebView *v, WebKitDownload *o, Client *c);
106 static gboolean keypress(GtkWidget *w, GdkEventKey *ev, Client *c);
107 static void linkhover(WebKitWebView *v, const char* t, const char* l, Client *c);
108 static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c);
109 static void loaduri(Client *c, const Arg *arg);
110 static void navigate(Client *c, const Arg *arg);
111 static Client *newclient(void);
112 static void newwindow(Client *c, const Arg *arg, gboolean noembed);
113 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
114 static void populatepopup(WebKitWebView *web, GtkMenu *menu, Client *c);
115 static void popupactivate(GtkMenuItem *menu, Client *);
116 static void print(Client *c, const Arg *arg);
117 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event, gpointer d);
118 static void progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c);
119 static void reload(Client *c, const Arg *arg);
120 static void scroll_h(Client *c, const Arg *arg);
121 static void scroll_v(Client *c, const Arg *arg);
122 static void scroll(GtkAdjustment *a, const Arg *arg);
123 static void setatom(Client *c, int a, const char *v);
124 static void setup(void);
125 static void sigchld(int unused);
126 static void source(Client *c, const Arg *arg);
127 static void spawn(Client *c, const Arg *arg);
128 static void eval(Client *c, const Arg *arg);
129 static void stop(Client *c, const Arg *arg);
130 static void titlechange(WebKitWebView *v, WebKitWebFrame* frame, const char* title, Client *c);
131 static void toggle(Client *c, const Arg *arg);
132 static void gettogglestat(Client *c);
133 static void update(Client *c);
134 static void updatewinid(Client *c);
135 static void usage(void);
136 static void windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js, JSObjectRef win, Client *c);
137 static void zoom(Client *c, const Arg *arg);
138
139 /* configuration, allows nested code to access above variables */
140 #include "config.h"
141
142 char *
143 buildpath(const char *path) {
144         char *apath, *p;
145         FILE *f;
146
147         /* creating directory */
148         if(path[0] == '/') {
149                 apath = g_strdup(path);
150         } else if(path[0] == '~') {
151                 if(path[1] == '/') {
152                         apath = g_strconcat(g_get_home_dir(), &path[1], NULL);
153                 } else {
154                         apath = g_strconcat(g_get_home_dir(), "/",
155                                         &path[1], NULL);
156                 }
157         } else {
158                 apath = g_strconcat(g_get_current_dir(), "/", path, NULL);
159         }
160
161         if((p = strrchr(apath, '/'))) {
162                 *p = '\0';
163                 g_mkdir_with_parents(apath, 0700);
164                 g_chmod(apath, 0700); /* in case it existed */
165                 *p = '/';
166         }
167         /* creating file (gives error when apath ends with "/") */
168         if((f = fopen(apath, "a"))) {
169                 g_chmod(apath, 0600); /* always */
170                 fclose(f);
171         }
172
173         return apath;
174 }
175
176 static gboolean
177 buttonrelease(WebKitWebView *web, GdkEventButton *e, GList *gl) {
178         WebKitHitTestResultContext context;
179         WebKitHitTestResult *result = webkit_web_view_get_hit_test_result(web, e);
180         Arg arg;
181
182         g_object_get(result, "context", &context, NULL);
183         if(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
184                 if(e->button == 2) {
185                         g_object_get(result, "link-uri", &arg.v, NULL);
186                         newwindow(NULL, &arg, e->state & GDK_CONTROL_MASK);
187                         return true;
188                 }
189         }
190         return false;
191 }
192
193 void
194 cleanup(void) {
195         while(clients)
196                 destroyclient(clients);
197         g_free(cookiefile);
198         g_free(scriptfile);
199         g_free(stylefile);
200 }
201
202 static void
203 cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, SoupCookie *new_cookie) {
204         flock(COOKIEJAR(self)->lock, LOCK_EX);
205         if(new_cookie && !new_cookie->expires && sessiontime)
206                 soup_cookie_set_expires(new_cookie, soup_date_new_from_now(sessiontime));
207         SOUP_COOKIE_JAR_CLASS(cookiejar_parent_class)->changed(self, old_cookie, new_cookie);
208         flock(COOKIEJAR(self)->lock, LOCK_UN);
209 }
210
211 static void
212 cookiejar_class_init(CookieJarClass *klass) {
213         SOUP_COOKIE_JAR_CLASS(klass)->changed = cookiejar_changed;
214         G_OBJECT_CLASS(klass)->get_property = G_OBJECT_CLASS(cookiejar_parent_class)->get_property;
215         G_OBJECT_CLASS(klass)->set_property = cookiejar_set_property;
216         G_OBJECT_CLASS(klass)->finalize = cookiejar_finalize;
217         g_object_class_override_property(G_OBJECT_CLASS(klass), 1, "filename");
218 }
219
220 static void
221 cookiejar_finalize(GObject *self) {
222         close(COOKIEJAR(self)->lock);
223         G_OBJECT_CLASS(cookiejar_parent_class)->finalize(self);
224 }
225
226 static void
227 cookiejar_init(CookieJar *self) {
228         self->lock = open(cookiefile, 0);
229 }
230
231 static SoupCookieJar *
232 cookiejar_new(const char *filename, gboolean read_only) {
233         return g_object_new(COOKIEJAR_TYPE,
234                             SOUP_COOKIE_JAR_TEXT_FILENAME, filename,
235                             SOUP_COOKIE_JAR_READ_ONLY, read_only, NULL);
236 }
237
238 static void
239 cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec) {
240         flock(COOKIEJAR(self)->lock, LOCK_SH);
241         G_OBJECT_CLASS(cookiejar_parent_class)->set_property(self, prop_id, value, pspec);
242         flock(COOKIEJAR(self)->lock, LOCK_UN);
243 }
244
245 void
246 evalscript(JSContextRef js, char *script, char* scriptname) {
247         JSStringRef jsscript, jsscriptname;
248         JSValueRef exception = NULL;
249
250         jsscript = JSStringCreateWithUTF8CString(script);
251         jsscriptname = JSStringCreateWithUTF8CString(scriptname);
252         JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js), jsscriptname, 0, &exception);
253         JSStringRelease(jsscript);
254         JSStringRelease(jsscriptname);
255 }
256
257 void
258 runscript(WebKitWebFrame *frame) {
259         char *script;
260         GError *error;
261
262         if(g_file_get_contents(scriptfile, &script, NULL, &error)) {
263                 evalscript(webkit_web_frame_get_global_context(frame), script, scriptfile);
264         }
265 }
266
267 void
268 clipboard(Client *c, const Arg *arg) {
269         gboolean paste = *(gboolean *)arg;
270
271         if(paste)
272                 gtk_clipboard_request_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), pasteuri, c);
273         else
274                 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), c->linkhover ? c->linkhover : geturi(c), -1);
275 }
276
277 char *
278 copystr(char **str, const char *src) {
279         char *tmp;
280         tmp = g_strdup(src);
281
282         if(str && *str) {
283                 g_free(*str);
284                 *str = tmp;
285         }
286         return tmp;
287 }
288
289 WebKitWebView *
290 createwindow(WebKitWebView  *v, WebKitWebFrame *f, Client *c) {
291         Client *n = newclient();
292         return n->view;
293 }
294
295 gboolean
296 decidedownload(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r, gchar *m,  WebKitWebPolicyDecision *p, Client *c) {
297         if(!webkit_web_view_can_show_mime_type(v, m)) {
298                 webkit_web_policy_decision_download(p);
299                 return TRUE;
300         }
301         return FALSE;
302 }
303
304 gboolean
305 decidewindow(WebKitWebView *view, WebKitWebFrame *f, WebKitNetworkRequest *r, WebKitWebNavigationAction *n, WebKitWebPolicyDecision *p, Client *c) {
306         Arg arg;
307
308         if(webkit_web_navigation_action_get_reason(n) == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
309                 webkit_web_policy_decision_ignore(p);
310                 arg.v = (void *)webkit_network_request_get_uri(r);
311                 newwindow(NULL, &arg, 0);
312                 return TRUE;
313         }
314         return FALSE;
315 }
316
317 void
318 destroyclient(Client *c) {
319         Client *p;
320
321         webkit_web_view_stop_loading(c->view);
322         gtk_widget_destroy(c->indicator);
323         gtk_widget_destroy(GTK_WIDGET(c->view));
324         gtk_widget_destroy(c->scroll);
325         gtk_widget_destroy(c->vbox);
326         gtk_widget_destroy(c->win);
327
328         for(p = clients; p && p->next != c; p = p->next);
329         if(p)
330                 p->next = c->next;
331         else
332                 clients = c->next;
333         free(c);
334         if(clients == NULL)
335                 gtk_main_quit();
336 }
337
338 void
339 destroywin(GtkWidget* w, Client *c) {
340         destroyclient(c);
341 }
342
343 void
344 die(char *str) {
345         fputs(str, stderr);
346         exit(EXIT_FAILURE);
347 }
348
349 void
350 drawindicator(Client *c) {
351         gint width;
352         const char *uri;
353         char *colorname;
354         GtkWidget *w;
355         GdkGC *gc;
356         GdkColor fg;
357
358         uri = geturi(c);
359         w = c->indicator;
360         width = c->progress * w->allocation.width / 100;
361         gc = gdk_gc_new(w->window);
362         if(strstr(uri, "https://") == uri) {
363                 if(usingproxy) {
364                         colorname = c->sslfailed? progress_proxy_untrust : progress_proxy_trust;
365                 } else {
366                         colorname = c->sslfailed? progress_untrust : progress_trust;
367                 }
368         } else {
369                 if(usingproxy) {
370                         colorname = progress_proxy;
371                 } else {
372                         colorname = progress;
373                 }
374         }
375
376         gdk_color_parse(colorname, &fg);
377         gdk_gc_set_rgb_fg_color(gc, &fg);
378         gdk_draw_rectangle(w->window,
379                         w->style->bg_gc[GTK_WIDGET_STATE(w)],
380                         TRUE, 0, 0, w->allocation.width, w->allocation.height);
381         gdk_draw_rectangle(w->window, gc, TRUE, 0, 0, width,
382                         w->allocation.height);
383         g_object_unref(gc);
384 }
385
386 gboolean
387 exposeindicator(GtkWidget *w, GdkEventExpose *e, Client *c) {
388         drawindicator(c);
389         return TRUE;
390 }
391
392 void
393 find(Client *c, const Arg *arg) {
394         const char *s;
395
396         s = getatom(c, AtomFind);
397         gboolean forward = *(gboolean *)arg;
398         webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE);
399 }
400
401 const char *
402 getatom(Client *c, int a) {
403         static char buf[BUFSIZ];
404         Atom adummy;
405         int idummy;
406         unsigned long ldummy;
407         unsigned char *p = NULL;
408
409         XGetWindowProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window),
410                         atoms[a], 0L, BUFSIZ, False, XA_STRING,
411                         &adummy, &idummy, &ldummy, &ldummy, &p);
412         if(p)
413                 strncpy(buf, (char *)p, LENGTH(buf)-1);
414         else
415                 buf[0] = '\0';
416         XFree(p);
417         return buf;
418 }
419
420 char *
421 geturi(Client *c) {
422         char *uri;
423
424         if(!(uri = (char *)webkit_web_view_get_uri(c->view)))
425                 uri = "about:blank";
426         return uri;
427 }
428
429 gboolean
430 initdownload(WebKitWebView *view, WebKitDownload *o, Client *c) {
431         Arg arg;
432
433         updatewinid(c);
434         arg = (Arg)DOWNLOAD((char *)webkit_download_get_uri(o), geturi(c));
435         spawn(c, &arg);
436         return FALSE;
437 }
438
439 gboolean
440 keypress(GtkWidget* w, GdkEventKey *ev, Client *c) {
441         guint i;
442         gboolean processed = FALSE;
443
444         updatewinid(c);
445         for(i = 0; i < LENGTH(keys); i++) {
446                 if(gdk_keyval_to_lower(ev->keyval) == keys[i].keyval
447                                 && (ev->state & keys[i].mod) == keys[i].mod
448                                 && keys[i].func) {
449                         keys[i].func(c, &(keys[i].arg));
450                         processed = TRUE;
451                 }
452         }
453
454         return processed;
455 }
456
457 void
458 linkhover(WebKitWebView *v, const char* t, const char* l, Client *c) {
459         if(l) {
460                 c->linkhover = copystr(&c->linkhover, l);
461         } else if(c->linkhover) {
462                 free(c->linkhover);
463                 c->linkhover = NULL;
464         }
465         update(c);
466 }
467
468 void
469 loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
470         WebKitWebFrame *frame;
471         WebKitWebDataSource *src;
472         WebKitNetworkRequest *request;
473         SoupMessage *msg;
474         char *uri;
475
476         switch(webkit_web_view_get_load_status (c->view)) {
477         case WEBKIT_LOAD_COMMITTED:
478                 uri = geturi(c);
479                 if(strstr(uri, "https://") == uri) {
480                         frame = webkit_web_view_get_main_frame(c->view);
481                         src = webkit_web_frame_get_data_source(frame);
482                         request = webkit_web_data_source_get_request(src);
483                         msg = webkit_network_request_get_message(request);
484                         c->sslfailed = soup_message_get_flags(msg)
485                                        ^ SOUP_MESSAGE_CERTIFICATE_TRUSTED;
486                 }
487                 setatom(c, AtomUri, uri);
488                 break;
489         case WEBKIT_LOAD_FINISHED:
490                 c->progress = 100;
491                 update(c);
492                 break;
493         default:
494                 break;
495         }
496 }
497
498 void
499 loaduri(Client *c, const Arg *arg) {
500         char *u;
501         const char *uri = (char *)arg->v;
502         Arg a = { .b = FALSE };
503
504         if(strcmp(uri, "") == 0)
505                 return;
506         u = g_strrstr(uri, "://") ? g_strdup(uri)
507                 : g_strdup_printf("http://%s", uri);
508         /* prevents endless loop */
509         if(c->uri && strcmp(u, c->uri) == 0) {
510                 reload(c, &a);
511         } else {
512                 webkit_web_view_load_uri(c->view, u);
513                 c->progress = 0;
514                 c->title = copystr(&c->title, u);
515                 g_free(u);
516                 update(c);
517         }
518 }
519
520 void
521 navigate(Client *c, const Arg *arg) {
522         int steps = *(int *)arg;
523         webkit_web_view_go_back_or_forward(c->view, steps);
524 }
525
526 Client *
527 newclient(void) {
528         Client *c;
529         WebKitWebSettings *settings;
530         WebKitWebFrame *frame;
531         GdkGeometry hints = { 1, 1 };
532         char *uri, *ua;
533
534         if(!(c = calloc(1, sizeof(Client))))
535                 die("Cannot malloc!\n");
536
537         /* Window */
538         if(embed) {
539                 c->win = gtk_plug_new(embed);
540         } else {
541                 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
542
543                 /* TA:  20091214:  Despite what the GNOME docs say, the ICCCM
544                  * is always correct, so we should still call this function.
545                  * But when doing so, we *must* differentiate between a
546                  * WM_CLASS and a resource on the window.  By convention, the
547                  * window class (WM_CLASS) is capped, while the resource is in
548                  * lowercase.   Both these values come as a pair.
549                  */
550                 gtk_window_set_wmclass(GTK_WINDOW(c->win), "surf", "Surf");
551
552                 /* TA:  20091214:  And set the role here as well -- so that
553                  * sessions can pick this up.
554                  */
555                 gtk_window_set_role(GTK_WINDOW(c->win), "Surf");
556         }
557         gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600);
558         g_signal_connect(G_OBJECT(c->win), "destroy", G_CALLBACK(destroywin), c);
559         g_signal_connect(G_OBJECT(c->win), "key-press-event", G_CALLBACK(keypress), c);
560
561         /* VBox */
562         c->vbox = gtk_vbox_new(FALSE, 0);
563
564         /* Scrolled Window */
565         c->scroll = gtk_scrolled_window_new(NULL, NULL);
566         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
567                         GTK_POLICY_NEVER, GTK_POLICY_NEVER);
568
569         /* Webview */
570         c->view = WEBKIT_WEB_VIEW(webkit_web_view_new());
571         g_signal_connect(G_OBJECT(c->view), "title-changed", G_CALLBACK(titlechange), c);
572         g_signal_connect(G_OBJECT(c->view), "hovering-over-link", G_CALLBACK(linkhover), c);
573         g_signal_connect(G_OBJECT(c->view), "create-web-view", G_CALLBACK(createwindow), c);
574         g_signal_connect(G_OBJECT(c->view), "new-window-policy-decision-requested", G_CALLBACK(decidewindow), c);
575         g_signal_connect(G_OBJECT(c->view), "mime-type-policy-decision-requested", G_CALLBACK(decidedownload), c);
576         g_signal_connect(G_OBJECT(c->view), "window-object-cleared", G_CALLBACK(windowobjectcleared), c);
577         g_signal_connect(G_OBJECT(c->view), "notify::load-status", G_CALLBACK(loadstatuschange), c);
578         g_signal_connect(G_OBJECT(c->view), "notify::progress", G_CALLBACK(progresschange), c);
579         g_signal_connect(G_OBJECT(c->view), "download-requested", G_CALLBACK(initdownload), c);
580         g_signal_connect(G_OBJECT(c->view), "button-release-event", G_CALLBACK(buttonrelease), c);
581         g_signal_connect(G_OBJECT(c->view), "populate-popup", G_CALLBACK(populatepopup), c);
582
583         /* Indicator */
584         c->indicator = gtk_drawing_area_new();
585         gtk_widget_set_size_request(c->indicator, 0, indicator_thickness);
586         g_signal_connect (G_OBJECT (c->indicator), "expose_event",
587                         G_CALLBACK (exposeindicator), c);
588
589         /* Arranging */
590         gtk_container_add(GTK_CONTAINER(c->scroll), GTK_WIDGET(c->view));
591         gtk_container_add(GTK_CONTAINER(c->win), c->vbox);
592         gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll);
593         gtk_container_add(GTK_CONTAINER(c->vbox), c->indicator);
594
595         /* Setup */
596         gtk_box_set_child_packing(GTK_BOX(c->vbox), c->indicator, FALSE, FALSE, 0, GTK_PACK_START);
597         gtk_box_set_child_packing(GTK_BOX(c->vbox), c->scroll, TRUE, TRUE, 0, GTK_PACK_START);
598         gtk_widget_grab_focus(GTK_WIDGET(c->view));
599         gtk_widget_show(c->vbox);
600         gtk_widget_show(c->scroll);
601         gtk_widget_show(GTK_WIDGET(c->view));
602         gtk_widget_show(c->win);
603         gtk_window_set_geometry_hints(GTK_WINDOW(c->win), NULL, &hints, GDK_HINT_MIN_SIZE);
604         gdk_window_set_events(GTK_WIDGET(c->win)->window, GDK_ALL_EVENTS_MASK);
605         gdk_window_add_filter(GTK_WIDGET(c->win)->window, processx, c);
606         webkit_web_view_set_full_content_zoom(c->view, TRUE);
607
608         frame = webkit_web_view_get_main_frame(c->view);
609         runscript(frame);
610         settings = webkit_web_view_get_settings(c->view);
611         if(!(ua = getenv("SURF_USERAGENT")))
612                 ua = useragent;
613         g_object_set(G_OBJECT(settings), "user-agent", ua, NULL);
614         uri = g_strconcat("file://", stylefile, NULL);
615         g_object_set(G_OBJECT(settings), "user-stylesheet-uri", uri, NULL);
616         g_object_set(G_OBJECT(settings), "auto-load-images", loadimages, NULL);
617         g_object_set(G_OBJECT(settings), "enable-plugins", enableplugins, NULL);
618         g_object_set(G_OBJECT(settings), "enable-scripts", enablescripts, NULL);
619         g_object_set(G_OBJECT(settings), "enable-spatial-navigation",
620                         spatialbrowsing, NULL);
621
622         g_free(uri);
623
624         setatom(c, AtomFind, "");
625         setatom(c, AtomUri, "about:blank");
626         if(hidebackground)
627                 webkit_web_view_set_transparent(c->view, TRUE);
628
629         c->title = NULL;
630         c->next = clients;
631         clients = c;
632         if(showxid) {
633                 gdk_display_sync(gtk_widget_get_display(c->win));
634                 printf("%u\n", (guint)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window));
635                 fflush(NULL);
636                 if (fclose(stdout) != 0) {
637                         die("Error closing stdout");
638                 }
639         }
640         return c;
641 }
642
643 void
644 newwindow(Client *c, const Arg *arg, gboolean noembed) {
645         guint i = 0;
646         const char *cmd[10], *uri;
647         const Arg a = { .v = (void *)cmd };
648         char tmp[64];
649
650         cmd[i++] = argv0;
651         if(embed && !noembed) {
652                 cmd[i++] = "-e";
653                 snprintf(tmp, LENGTH(tmp), "%u\n", (int)embed);
654                 cmd[i++] = tmp;
655         }
656         if(!enablescripts)
657                 cmd[i++] = "-s";
658         if(!enableplugins)
659                 cmd[i++] = "-p";
660         if(!loadimages)
661                 cmd[i++] = "-i";
662         if(showxid)
663                 cmd[i++] = "-x";
664         cmd[i++] = "--";
665         uri = arg->v ? (char *)arg->v : c->linkhover;
666         if(uri)
667                 cmd[i++] = uri;
668         cmd[i++] = NULL;
669         spawn(NULL, &a);
670 }
671
672 static void
673 populatepopup(WebKitWebView *web, GtkMenu *menu, Client *c) {
674         GList *items = gtk_container_get_children(GTK_CONTAINER(menu));
675
676         for(GList *l = items; l; l = l->next) {
677                 g_signal_connect(l->data, "activate", G_CALLBACK(popupactivate), c);
678         }
679
680         g_list_free(items);
681 }
682
683 static void
684 popupactivate(GtkMenuItem *menu, Client *c) {
685         /*
686          * context-menu-action-2000     open link
687          * context-menu-action-1        open link in window
688          * context-menu-action-2        download linked file
689          * context-menu-action-3        copy link location
690          * context-menu-action-13       reload
691          * context-menu-action-10       back
692          * context-menu-action-11       forward
693          * context-menu-action-12       stop
694          */
695
696         GtkAction *a = NULL;
697         const char *name;
698         GtkClipboard *prisel;
699
700         a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(menu));
701         if(a == NULL)
702                 return;
703
704         name = gtk_action_get_name(a);
705         if(!g_strcmp0(name, "context-menu-action-3")) {
706                 prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
707                 gtk_clipboard_set_text(prisel, c->linkhover, -1);
708         }
709 }
710
711 void
712 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d) {
713         Arg arg = {.v = text };
714         if(text != NULL)
715                 loaduri((Client *) d, &arg);
716 }
717
718 void
719 print(Client *c, const Arg *arg) {
720         webkit_web_frame_print(webkit_web_view_get_main_frame(c->view));
721 }
722
723 GdkFilterReturn
724 processx(GdkXEvent *e, GdkEvent *event, gpointer d) {
725         Client *c = (Client *)d;
726         XPropertyEvent *ev;
727         Arg arg;
728
729         if(((XEvent *)e)->type == PropertyNotify) {
730                 ev = &((XEvent *)e)->xproperty;
731                 if(ev->state == PropertyNewValue) {
732                         if(ev->atom == atoms[AtomFind]) {
733                                 arg.b = TRUE;
734                                 find(c, &arg);
735                                 return GDK_FILTER_REMOVE;
736                         }
737                         else if(ev->atom == atoms[AtomGo]) {
738                                 arg.v = getatom(c, AtomGo);
739                                 loaduri(c, &arg);
740                                 return GDK_FILTER_REMOVE;
741                         }
742                 }
743         }
744         return GDK_FILTER_CONTINUE;
745 }
746
747 void
748 progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
749         c->progress = webkit_web_view_get_progress(c->view) * 100;
750         update(c);
751 }
752
753 void
754 reload(Client *c, const Arg *arg) {
755         gboolean nocache = *(gboolean *)arg;
756         if(nocache)
757                  webkit_web_view_reload_bypass_cache(c->view);
758         else
759                  webkit_web_view_reload(c->view);
760 }
761
762 void
763 scroll_h(Client *c, const Arg *arg) {
764         scroll(gtk_scrolled_window_get_hadjustment(
765                                 GTK_SCROLLED_WINDOW(c->scroll)), arg);
766 }
767
768 void
769 scroll_v(Client *c, const Arg *arg) {
770         scroll(gtk_scrolled_window_get_vadjustment(
771                                 GTK_SCROLLED_WINDOW(c->scroll)), arg);
772 }
773
774 void
775 scroll(GtkAdjustment *a, const Arg *arg) {
776         gdouble v;
777
778         v = gtk_adjustment_get_value(a);
779         switch (arg->i){
780         case +10000:
781         case -10000:
782                 v += gtk_adjustment_get_page_increment(a) *
783                         (arg->i / 10000);
784                 break;
785         case +20000:
786         case -20000:
787         default:
788                 v += gtk_adjustment_get_step_increment(a) * arg->i;
789         }
790
791         v = MAX(v, 0.0);
792         v = MIN(v, gtk_adjustment_get_upper(a) -
793                         gtk_adjustment_get_page_size(a));
794         gtk_adjustment_set_value(a, v);
795 }
796
797 void
798 setatom(Client *c, int a, const char *v) {
799         XSync(dpy, False);
800         XChangeProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window), atoms[a],
801                         XA_STRING, 8, PropModeReplace, (unsigned char *)v,
802                         strlen(v) + 1);
803 }
804
805 void
806 setup(void) {
807         char *proxy;
808         char *new_proxy;
809         SoupURI *puri;
810         SoupSession *s;
811
812         /* clean up any zombies immediately */
813         sigchld(0);
814         gtk_init(NULL, NULL);
815         if (!g_thread_supported())
816                 g_thread_init(NULL);
817
818         dpy = GDK_DISPLAY();
819
820         /* atoms */
821         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
822         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
823         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
824
825         /* dirs and files */
826         cookiefile = buildpath(cookiefile);
827         scriptfile = buildpath(scriptfile);
828         stylefile = buildpath(stylefile);
829
830         /* request handler */
831         s = webkit_get_default_session();
832
833         /* cookie jar */
834         soup_session_add_feature(s, SOUP_SESSION_FEATURE(cookiejar_new(cookiefile, FALSE)));
835
836         /* ssl */
837         g_object_set(G_OBJECT(s), "ssl-ca-file", cafile, NULL);
838         g_object_set(G_OBJECT(s), "ssl-strict", strictssl, NULL);
839
840         /* proxy */
841         if((proxy = getenv("http_proxy")) && strcmp(proxy, "")) {
842                 new_proxy = g_strrstr(proxy, "http://") ? g_strdup(proxy) :
843                         g_strdup_printf("http://%s", proxy);
844                 puri = soup_uri_new(new_proxy);
845                 g_object_set(G_OBJECT(s), "proxy-uri", puri, NULL);
846                 soup_uri_free(puri);
847                 g_free(new_proxy);
848                 usingproxy = 1;
849         }
850 }
851
852 void
853 sigchld(int unused) {
854         if(signal(SIGCHLD, sigchld) == SIG_ERR)
855                 die("Can't install SIGCHLD handler");
856         while(0 < waitpid(-1, NULL, WNOHANG));
857 }
858
859 void
860 source(Client *c, const Arg *arg) {
861         Arg a = { .b = FALSE };
862         gboolean s;
863
864         s = webkit_web_view_get_view_source_mode(c->view);
865         webkit_web_view_set_view_source_mode(c->view, !s);
866         reload(c, &a);
867 }
868
869 void
870 spawn(Client *c, const Arg *arg) {
871         if(fork() == 0) {
872                 if(dpy)
873                         close(ConnectionNumber(dpy));
874                 setsid();
875                 execvp(((char **)arg->v)[0], (char **)arg->v);
876                 fprintf(stderr, "surf: execvp %s", ((char **)arg->v)[0]);
877                 perror(" failed");
878                 exit(0);
879         }
880 }
881
882 void
883 eval(Client *c, const Arg *arg) {
884         WebKitWebFrame *frame = webkit_web_view_get_main_frame(c->view);
885         evalscript(webkit_web_frame_get_global_context(frame),
886                         ((char **)arg->v)[0], "");
887 }
888
889 void
890 stop(Client *c, const Arg *arg) {
891         webkit_web_view_stop_loading(c->view);
892 }
893
894 void
895 titlechange(WebKitWebView *v, WebKitWebFrame *f, const char *t, Client *c) {
896         c->title = copystr(&c->title, t);
897         update(c);
898 }
899
900 void
901 toggle(Client *c, const Arg *arg) {
902         WebKitWebSettings *settings;
903         char *name = (char *)arg->v;
904         gboolean value;
905         Arg a = { .b = FALSE };
906
907         settings = webkit_web_view_get_settings(c->view);
908         g_object_get(G_OBJECT(settings), name, &value, NULL);
909         g_object_set(G_OBJECT(settings), name, !value, NULL);
910
911         reload(c,&a);
912 }
913
914 void
915 gettogglestat(Client *c){
916         gboolean value;
917         WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
918
919         g_object_get(G_OBJECT(settings), "enable-caret-browsing",
920                         &value, NULL);
921         togglestat[0] = value? 'C': 'c';
922
923         g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL);
924         togglestat[1] = value? 'I': 'i';
925
926         g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL);
927         togglestat[2] = value? 'S': 's';
928
929         g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL);
930         togglestat[3] = value? 'V': 'v';
931
932         togglestat[4] = '\0';
933 }
934
935
936 void
937 update(Client *c) {
938         char *t;
939
940         gettogglestat(c);
941
942         if(c->linkhover) {
943                 t = g_strdup_printf("%s| %s", togglestat, c->linkhover);
944         } else if(c->progress != 100) {
945                 drawindicator(c);
946                 gtk_widget_show(c->indicator);
947                 t = g_strdup_printf("[%i%%] %s| %s", c->progress, togglestat,
948                                 c->title);
949         } else {
950                 gtk_widget_hide_all(c->indicator);
951                 t = g_strdup_printf("%s| %s", togglestat, c->title);
952         }
953
954         gtk_window_set_title(GTK_WINDOW(c->win), t);
955         g_free(t);
956 }
957
958 void
959 updatewinid(Client *c) {
960         snprintf(winid, LENGTH(winid), "%u",
961                         (int)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window));
962 }
963
964 void
965 usage(void) {
966         fputs("surf - simple browser\n", stderr);
967         die("usage: surf [-c cookiefile] [-e xid] [-i] [-p] [-r scriptfile]"
968                 " [-s] [-t stylefile] [-u useragent] [-v] [-x] [uri]\n");
969 }
970
971 void
972 windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js, JSObjectRef win, Client *c) {
973         runscript(frame);
974 }
975
976 void
977 zoom(Client *c, const Arg *arg) {
978         c->zoomed = TRUE;
979         if(arg->i < 0)          /* zoom out */
980                 webkit_web_view_zoom_out(c->view);
981         else if(arg->i > 0)     /* zoom in */
982                 webkit_web_view_zoom_in(c->view);
983         else {                  /* reset */
984                 c->zoomed = FALSE;
985                 webkit_web_view_set_zoom_level(c->view, 1.0);
986         }
987 }
988
989 int
990 main(int argc, char *argv[]) {
991         Arg arg;
992
993         memset(&arg, 0, sizeof(arg));
994
995         /* command line args */
996         ARGBEGIN {
997         case 'c':
998                 cookiefile = EARGF(usage());
999                 break;
1000         case 'e':
1001                 embed = strtol(EARGF(usage()), NULL, 0);
1002                 break;
1003         case 'i':
1004                 loadimages = 0;
1005                 break;
1006         case 'p':
1007                 enableplugins = 0;
1008                 break;
1009         case 'r':
1010                 scriptfile = EARGF(usage());
1011                 break;
1012         case 's':
1013                 enablescripts = 0;
1014                 break;
1015         case 't':
1016                 stylefile = EARGF(usage());
1017                 break;
1018         case 'u':
1019                 useragent = EARGF(usage());
1020                 break;
1021         case 'x':
1022                 showxid = TRUE;
1023                 break;
1024         case 'v':
1025                 die("surf-"VERSION", ©2009-2012 surf engineers, see LICENSE for details\n");
1026         default:
1027                 usage();
1028         } ARGEND;
1029         if(argc > 0)
1030                 arg.v = argv[0];
1031
1032         setup();
1033         newclient();
1034         if(arg.v)
1035                 loaduri(clients, &arg);
1036
1037         gtk_main();
1038         cleanup();
1039
1040         return EXIT_SUCCESS;
1041 }
1042