uzbl now builds on OpenBSD
[uzbl-mobile] / uzbl.c
1 /* -*- c-basic-offset: 4; -*- */
2 // Original code taken from the example webkit-gtk+ application. see notice below.
3 // Modified code is licensed under the GPL 3.  See LICENSE file.
4
5
6 /*
7  * Copyright (C) 2006, 2007 Apple Inc.
8  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32
33 #define LENGTH(x) (sizeof x / sizeof x[0])
34 #define MAX_BINDINGS 256
35
36 #include <gtk/gtk.h>
37 #include <gdk/gdkx.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/un.h>
43 #include <sys/utsname.h>
44 #include <webkit/webkit.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <stdlib.h>
49 #include <errno.h>
50 #include <string.h>
51 #include <fcntl.h>
52 #include <sys/socket.h>
53 #include <sys/un.h>
54 #include <libsoup/soup.h>
55 #include <signal.h>
56 #include "uzbl.h"
57 #include "config.h"
58
59 static Uzbl uzbl;
60 typedef void (*Command)(WebKitWebView*, GArray *argv);
61
62 /* commandline arguments (set initial values for the state variables) */
63 static GOptionEntry entries[] =
64 {
65     { "uri",     'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri,           "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
66     { "verbose", 'v', 0, G_OPTION_ARG_NONE,   &uzbl.state.verbose,       "Whether to print all messages or just errors.", NULL },
67     { "name",    'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
68     { "config",  'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file,   "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
69     { NULL,      0, 0, 0, NULL, NULL, NULL }
70 };
71
72
73 /* associate command names to their properties */
74 typedef const struct {
75     void **ptr;
76     int type;
77     void (*func)(void);
78 } uzbl_cmdprop;
79
80 enum {TYPE_INT, TYPE_STR};
81
82 /* an abbreviation to help keep the table's width humane */
83 #define PTR(var, t, fun) { .ptr = (void*)&(var), .type = TYPE_##t, .func = fun }
84
85 const struct {
86     char *name;
87     uzbl_cmdprop cp;
88 } var_name_to_ptr[] = {
89 /*    variable name         pointer to variable in code          type  callback function    */
90 /*  --------------------------------------------------------------------------------------- */
91     { "uri",                 PTR(uzbl.state.uri,                  STR, cmd_load_uri)},
92     { "status_message",      PTR(uzbl.gui.sbar.msg,               STR, update_title)},
93     { "show_status",         PTR(uzbl.behave.show_status,         INT, cmd_set_status)},
94     { "status_top",          PTR(uzbl.behave.status_top,          INT, move_statusbar)},
95     { "status_format",       PTR(uzbl.behave.status_format,       STR, update_title)},
96     { "status_pbar_done",    PTR(uzbl.gui.sbar.progress_s,        STR, update_title)},
97     { "status_pbar_pending", PTR(uzbl.gui.sbar.progress_u,        STR, update_title)},
98     { "status_pbar_width",   PTR(uzbl.gui.sbar.progress_w,        INT, update_title)},
99     { "status_background",   PTR(uzbl.behave.status_background,   STR, update_title)},
100     { "title_format_long",   PTR(uzbl.behave.title_format_long,   STR, update_title)},
101     { "title_format_short",  PTR(uzbl.behave.title_format_short,  STR, update_title)},
102     { "insert_mode",         PTR(uzbl.behave.insert_mode,         INT, NULL)},
103     { "always_insert_mode",  PTR(uzbl.behave.always_insert_mode,  INT, cmd_always_insert_mode)},
104     { "reset_command_mode",  PTR(uzbl.behave.reset_command_mode,  INT, NULL)},
105     { "modkey",              PTR(uzbl.behave.modkey,              STR, cmd_modkey)},
106     { "load_finish_handler", PTR(uzbl.behave.load_finish_handler, STR, NULL)},
107     { "load_start_handler",  PTR(uzbl.behave.load_start_handler,  STR, NULL)},
108     { "load_commit_handler", PTR(uzbl.behave.load_commit_handler, STR, NULL)},
109     { "history_handler",     PTR(uzbl.behave.history_handler,     STR, NULL)},
110     { "download_handler",    PTR(uzbl.behave.download_handler,    STR, NULL)},
111     { "cookie_handler",      PTR(uzbl.behave.cookie_handler,      STR, cmd_cookie_handler)},
112     { "fifo_dir",            PTR(uzbl.behave.fifo_dir,            STR, cmd_fifo_dir)},
113     { "socket_dir",          PTR(uzbl.behave.socket_dir,          STR, cmd_socket_dir)},
114     { "http_debug",          PTR(uzbl.behave.http_debug,          INT, cmd_http_debug)},
115     { "font_size",           PTR(uzbl.behave.font_size,           INT, cmd_font_size)},
116     { "monospace_size",      PTR(uzbl.behave.monospace_size,      INT, cmd_font_size)},
117     { "minimum_font_size",   PTR(uzbl.behave.minimum_font_size,   INT, cmd_minimum_font_size)},
118     { "disable_plugins",     PTR(uzbl.behave.disable_plugins,     INT, cmd_disable_plugins)},
119     { "shell_cmd",           PTR(uzbl.behave.shell_cmd,           STR, NULL)},
120     { "proxy_url",           PTR(uzbl.net.proxy_url,              STR, set_proxy_url)},
121     { "max_conns",           PTR(uzbl.net.max_conns,              INT, cmd_max_conns)},
122     { "max_conns_host",      PTR(uzbl.net.max_conns_host,         INT, cmd_max_conns_host)},
123     { "useragent",           PTR(uzbl.net.useragent,              STR, cmd_useragent)},
124     { NULL,                  {.ptr = NULL, .type = TYPE_INT, .func = NULL}}
125 }, *n2v_p = var_name_to_ptr;
126
127 const struct {
128     char *key;
129     guint mask;
130 } modkeys[] = {
131     { "SHIFT",   GDK_SHIFT_MASK   }, // shift
132     { "LOCK",    GDK_LOCK_MASK    }, // capslock or shiftlock, depending on xserver's modmappings
133     { "CONTROL", GDK_CONTROL_MASK }, // control
134     { "MOD1",    GDK_MOD1_MASK    }, // 4th mod - normally alt but depends on modmappings
135     { "MOD2",    GDK_MOD2_MASK    }, // 5th mod
136     { "MOD3",    GDK_MOD3_MASK    }, // 6th mod
137     { "MOD4",    GDK_MOD4_MASK    }, // 7th mod
138     { "MOD5",    GDK_MOD5_MASK    }, // 8th mod
139     { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
140     { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
141     { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
142     { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
143     { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
144     { "SUPER",   GDK_SUPER_MASK   }, // super (since 2.10)
145     { "HYPER",   GDK_HYPER_MASK   }, // hyper (since 2.10)
146     { "META",    GDK_META_MASK    }, // meta (since 2.10)
147     { NULL,      0                }
148 };
149
150
151 /* construct a hash from the var_name_to_ptr array for quick access */
152 static void
153 make_var_to_name_hash() {
154     uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
155     while(n2v_p->name) {
156         g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
157         n2v_p++;
158     }
159 }
160
161
162 /* --- UTILITY FUNCTIONS --- */
163
164 char *
165 itos(int val) {
166     char tmp[20];
167
168     snprintf(tmp, sizeof(tmp), "%i", val);
169     return g_strdup(tmp);
170 }
171
172 static gchar*
173 strfree(gchar *str) { g_free(str); return NULL; }  // for freeing & setting to null in one go
174
175 static gchar*
176 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
177
178 static char *
179 str_replace (const char* search, const char* replace, const char* string) {
180     gchar **buf;
181     char *ret;
182
183     buf = g_strsplit (string, search, -1);
184     ret = g_strjoinv (replace, buf);
185     g_strfreev(buf); // somebody said this segfaults
186
187     return ret;
188 }
189
190 static GArray*
191 read_file_by_line (gchar *path) {
192     GIOChannel *chan = NULL;
193     gchar *readbuf = NULL;
194     gsize len;
195     GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
196     int i = 0;
197     
198     chan = g_io_channel_new_file(path, "r", NULL);
199     
200     if (chan) {
201         while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
202             const gchar* val = g_strdup (readbuf);
203             g_array_append_val (lines, val);
204             g_free (readbuf);
205             i ++;
206         }
207         
208         g_io_channel_unref (chan);
209     } else {
210         fprintf(stderr, "File '%s' not be read.\n", path);
211     }
212     
213     return lines;
214 }
215
216 static
217 gchar* parseenv (char* string) {
218     extern char** environ;
219     gchar* tmpstr = NULL;
220     int i = 0;
221     
222
223     while (environ[i] != NULL) {
224         gchar** env = g_strsplit (environ[i], "=", 2);
225         gchar* envname = g_strconcat ("$", env[0], NULL);
226
227         if (g_strrstr (string, envname) != NULL) {
228             tmpstr = g_strdup(string);
229             g_free (string);
230             string = str_replace(envname, env[1], tmpstr);
231             g_free (tmpstr);
232         }
233
234         g_free (envname);
235         g_strfreev (env); // somebody said this breaks uzbl
236         i++;
237     }
238
239     return string;
240 }
241
242 static sigfunc*
243 setup_signal(int signr, sigfunc *shandler) {
244     struct sigaction nh, oh;
245
246     nh.sa_handler = shandler;
247     sigemptyset(&nh.sa_mask);
248     nh.sa_flags = 0;
249
250     if(sigaction(signr, &nh, &oh) < 0)
251         return SIG_ERR;
252
253     return NULL;
254 }
255
256 static void
257 clean_up(void) {
258     if (uzbl.behave.fifo_dir)
259         unlink (uzbl.comm.fifo_path);
260     if (uzbl.behave.socket_dir)
261         unlink (uzbl.comm.socket_path);
262
263     g_free(uzbl.state.executable_path);
264     g_string_free(uzbl.state.keycmd, TRUE);
265     g_hash_table_destroy(uzbl.bindings);
266     g_hash_table_destroy(uzbl.behave.commands);
267 }
268
269
270 /* --- SIGNAL HANDLER --- */
271
272 static void
273 catch_sigterm(int s) {
274     (void) s;
275     clean_up();
276 }
277
278 static void
279 catch_sigint(int s) {
280     (void) s;
281     clean_up();
282     exit(EXIT_SUCCESS);
283 }
284
285 /* --- CALLBACKS --- */
286
287 static gboolean
288 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
289     (void) web_view;
290     (void) frame;
291     (void) navigation_action;
292     (void) policy_decision;
293     (void) user_data;
294     const gchar* uri = webkit_network_request_get_uri (request);
295     if (uzbl.state.verbose)
296         printf("New window requested -> %s \n", uri);
297     new_window_load_uri(uri);
298     return (FALSE);
299 }
300
301 WebKitWebView*
302 create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer user_data) {
303     (void) web_view;
304     (void) frame;
305     (void) user_data;
306     if (uzbl.state.selected_url != NULL) {
307         if (uzbl.state.verbose)
308             printf("\nNew web view -> %s\n",uzbl.state.selected_url);
309         new_window_load_uri(uzbl.state.selected_url);
310     } else {
311         if (uzbl.state.verbose)
312             printf("New web view -> %s\n","Nothing to open, exiting");
313     }
314     return (NULL);
315 }
316
317 static gboolean
318 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
319     (void) web_view;
320     (void) user_data;
321     if (uzbl.behave.download_handler) {
322         const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
323         if (uzbl.state.verbose)
324             printf("Download -> %s\n",uri);
325         /* if urls not escaped, we may have to escape and quote uri before this call */
326         run_handler(uzbl.behave.download_handler, uri);
327     }
328     return (FALSE);
329 }
330
331 /* scroll a bar in a given direction */
332 static void
333 scroll (GtkAdjustment* bar, GArray *argv) {
334     gdouble amount;
335     gchar *end;
336
337     amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
338     if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
339     gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
340 }
341
342 static void scroll_begin(WebKitWebView* page, GArray *argv) {
343     (void) page; (void) argv;
344     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
345 }
346
347 static void scroll_end(WebKitWebView* page, GArray *argv) {
348     (void) page; (void) argv;
349     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
350                               gtk_adjustment_get_page_size(uzbl.gui.bar_v));
351 }
352
353 static void scroll_vert(WebKitWebView* page, GArray *argv) {
354     (void) page;
355     scroll(uzbl.gui.bar_v, argv);
356 }
357
358 static void scroll_horz(WebKitWebView* page, GArray *argv) {
359     (void) page;
360     scroll(uzbl.gui.bar_h, argv);
361 }
362
363 static void
364 cmd_set_status() {
365     if (!uzbl.behave.show_status) {
366         gtk_widget_hide(uzbl.gui.mainbar);
367     } else {
368         gtk_widget_show(uzbl.gui.mainbar);
369     }
370     update_title();
371 }
372
373 static void
374 toggle_status_cb (WebKitWebView* page, GArray *argv) {
375     (void)page;
376     (void)argv;
377
378     if (uzbl.behave.show_status) {
379         gtk_widget_hide(uzbl.gui.mainbar);
380     } else {
381         gtk_widget_show(uzbl.gui.mainbar);
382     }
383     uzbl.behave.show_status = !uzbl.behave.show_status;
384     update_title();
385 }
386
387 static void
388 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
389     (void) page;
390     (void) title;
391     (void) data;
392     //Set selected_url state variable
393     g_free(uzbl.state.selected_url);
394     uzbl.state.selected_url = NULL;
395     if (link) {
396         uzbl.state.selected_url = g_strdup(link);
397     }
398     update_title();
399 }
400
401 static void
402 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
403     (void) web_view;
404     (void) web_frame;
405     (void) data;
406     if (uzbl.gui.main_title)
407         g_free (uzbl.gui.main_title);
408     uzbl.gui.main_title = g_strdup (title);
409     update_title();
410 }
411
412 static void
413 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
414     (void) page;
415     (void) data;
416     uzbl.gui.sbar.load_progress = progress;
417     update_title();
418 }
419
420 static void
421 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
422     (void) page;
423     (void) frame;
424     (void) data;
425     if (uzbl.behave.load_finish_handler)
426         run_handler(uzbl.behave.load_finish_handler, "");
427 }
428
429 static void
430 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
431     (void) page;
432     (void) frame;
433     (void) data;
434     g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
435     if (uzbl.behave.load_start_handler)
436         run_handler(uzbl.behave.load_start_handler, "");
437 }
438
439 static void
440 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
441     (void) page;
442     (void) data;
443     g_free (uzbl.state.uri);
444     GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
445     uzbl.state.uri = g_string_free (newuri, FALSE);
446     if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
447         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
448         update_title();
449     }
450     if (uzbl.behave.load_commit_handler)
451         run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
452 }
453
454 static void
455 destroy_cb (GtkWidget* widget, gpointer data) {
456     (void) widget;
457     (void) data;
458     gtk_main_quit ();
459 }
460
461 static void
462 log_history_cb () {
463    if (uzbl.behave.history_handler) {
464        time_t rawtime;
465        struct tm * timeinfo;
466        char date [80];
467        time ( &rawtime );
468        timeinfo = localtime ( &rawtime );
469        strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
470        run_handler(uzbl.behave.history_handler, date);
471    }
472 }
473
474
475 /* VIEW funcs (little webkit wrappers) */
476 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
477 VIEWFUNC(reload)
478 VIEWFUNC(reload_bypass_cache)
479 VIEWFUNC(stop_loading)
480 VIEWFUNC(zoom_in)
481 VIEWFUNC(zoom_out)
482 VIEWFUNC(go_back)
483 VIEWFUNC(go_forward)
484 #undef VIEWFUNC
485
486 /* -- command to callback/function map for things we cannot attach to any signals */
487 // TODO: reload
488 static struct {char *name; Command command[2];} cmdlist[] =
489 {   /* key                   function      no_split      */
490     { "back",               {view_go_back, 0}              },
491     { "forward",            {view_go_forward, 0}           },
492     { "scroll_vert",        {scroll_vert, 0}               },
493     { "scroll_horz",        {scroll_horz, 0}               },
494     { "scroll_begin",       {scroll_begin, 0}              },
495     { "scroll_end",         {scroll_end, 0}                },
496     { "reload",             {view_reload, 0},              },
497     { "reload_ign_cache",   {view_reload_bypass_cache, 0}  },
498     { "stop",               {view_stop_loading, 0},        },
499     { "zoom_in",            {view_zoom_in, 0},             }, //Can crash (when max zoom reached?).
500     { "zoom_out",           {view_zoom_out, 0},            },
501     { "uri",                {load_uri, NOSPLIT}            },
502     { "js",                 {run_js, NOSPLIT}              },
503     { "script",             {run_external_js, 0}           },
504     { "toggle_status",      {toggle_status_cb, 0}          },
505     { "spawn",              {spawn, 0}                     },
506     { "sync_spawn",         {spawn_sync, 0}                }, // needed for cookie handler
507     { "sh",                 {spawn_sh, 0}                  },
508     { "sync_sh",            {spawn_sh_sync, 0}             }, // needed for cookie handler
509     { "exit",               {close_uzbl, 0}                },
510     { "search",             {search_forward_text, NOSPLIT} },
511     { "search_reverse",     {search_reverse_text, NOSPLIT} },
512     { "toggle_insert_mode", {toggle_insert_mode, 0}        },
513     { "runcmd",             {runcmd, NOSPLIT}              }
514 };
515
516 static void
517 commands_hash(void)
518 {
519     unsigned int i;
520     uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
521
522     for (i = 0; i < LENGTH(cmdlist); i++)
523         g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
524 }
525
526 /* -- CORE FUNCTIONS -- */
527
528 void
529 free_action(gpointer act) {
530     Action *action = (Action*)act;
531     g_free(action->name);
532     if (action->param)
533         g_free(action->param);
534     g_free(action);
535 }
536
537 Action*
538 new_action(const gchar *name, const gchar *param) {
539     Action *action = g_new(Action, 1);
540
541     action->name = g_strdup(name);
542     if (param)
543         action->param = g_strdup(param);
544     else
545         action->param = NULL;
546
547     return action;
548 }
549
550 static bool
551 file_exists (const char * filename) {
552     return (access(filename, F_OK) == 0);
553 }
554
555 static void
556 toggle_insert_mode(WebKitWebView *page, GArray *argv) {
557     (void)page;
558
559     if (argv_idx(argv, 0)) {
560         if (strcmp (argv_idx(argv, 0), "0") == 0) {
561             uzbl.behave.insert_mode = FALSE;
562         } else {
563             uzbl.behave.insert_mode = TRUE;
564         }
565     } else {
566         uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
567     }
568
569     update_title();
570 }
571
572 static void
573 load_uri (WebKitWebView *web_view, GArray *argv) {
574     if (argv_idx(argv, 0)) {
575         GString* newuri = g_string_new (argv_idx(argv, 0));
576         if (g_strrstr (argv_idx(argv, 0), "://") == NULL)
577             g_string_prepend (newuri, "http://");
578         /* if we do handle cookies, ask our handler for them */
579         webkit_web_view_load_uri (web_view, newuri->str);
580         g_string_free (newuri, TRUE);
581     }
582 }
583
584 static void
585 run_js (WebKitWebView * web_view, GArray *argv) {
586     if (argv_idx(argv, 0))
587         webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
588 }
589
590 static void
591 run_external_js (WebKitWebView * web_view, GArray *argv) {
592     if (argv_idx(argv, 0)) {
593         GArray* lines = read_file_by_line (argv_idx (argv, 0));
594         gchar*  js = NULL;
595         int i = 0;
596         gchar* line;
597
598         while ((line = g_array_index(lines, gchar*, i))) {
599             if (js == NULL) {
600                 js = g_strdup (line);
601             } else {
602                 gchar* newjs = g_strconcat (js, line, NULL);
603                 js = newjs;
604             }
605             i ++;
606             g_free (line);
607         }
608         
609         if (uzbl.state.verbose)
610             printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0));
611
612         if (argv_idx (argv, 1)) {
613             gchar* newjs = str_replace("%s", argv_idx (argv, 1), js);
614             g_free (js);
615             js = newjs;
616         }
617         webkit_web_view_execute_script (web_view, js);
618         g_free (js);
619         g_array_free (lines, TRUE);
620     }
621 }
622
623 static void
624 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
625     if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) {
626         if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) {
627             webkit_web_view_unmark_text_matches (page);
628             webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0);
629             uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
630         }
631     }
632     
633     if (uzbl.state.searchtx) {
634         if (uzbl.state.verbose)
635             printf ("Searching: %s\n", uzbl.state.searchtx);
636         webkit_web_view_set_highlight_text_matches (page, TRUE);
637         webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
638     }
639 }
640
641 static void
642 search_forward_text (WebKitWebView *page, GArray *argv) {
643     search_text(page, argv, TRUE);
644 }
645
646 static void
647 search_reverse_text (WebKitWebView *page, GArray *argv) {
648     search_text(page, argv, FALSE);
649 }
650
651 static void
652 new_window_load_uri (const gchar * uri) {
653     GString* to_execute = g_string_new ("");
654     g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
655     int i;
656     for (i = 0; entries[i].long_name != NULL; i++) {
657         if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
658             gchar** str = (gchar**)entries[i].arg_data;
659             if (*str!=NULL) {
660                 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
661             }
662         }
663     }
664     if (uzbl.state.verbose)
665         printf("\n%s\n", to_execute->str);
666     g_spawn_command_line_async (to_execute->str, NULL);
667     g_string_free (to_execute, TRUE);
668 }
669
670 static void
671 close_uzbl (WebKitWebView *page, GArray *argv) {
672     (void)page;
673     (void)argv;
674     gtk_main_quit ();
675 }
676
677 /* --Statusbar functions-- */
678 static char*
679 build_progressbar_ascii(int percent) {
680    int width=uzbl.gui.sbar.progress_w;
681    int i;
682    double l;
683    GString *bar = g_string_new("");
684
685    l = (double)percent*((double)width/100.);
686    l = (int)(l+.5)>=(int)l ? l+.5 : l;
687
688    for(i=0; i<(int)l; i++)
689        g_string_append(bar, uzbl.gui.sbar.progress_s);
690
691    for(; i<width; i++)
692        g_string_append(bar, uzbl.gui.sbar.progress_u);
693
694    return g_string_free(bar, FALSE);
695 }
696
697 static void
698 setup_scanner() {
699      const GScannerConfig scan_config = {
700              (
701               "\t\r\n"
702              )            /* cset_skip_characters */,
703              (
704               G_CSET_a_2_z
705               "_#"
706               G_CSET_A_2_Z
707              )            /* cset_identifier_first */,
708              (
709               G_CSET_a_2_z
710               "_0123456789"
711               G_CSET_A_2_Z
712               G_CSET_LATINS
713               G_CSET_LATINC
714              )            /* cset_identifier_nth */,
715              ( "" )    /* cpair_comment_single */,
716
717              TRUE         /* case_sensitive */,
718
719              FALSE        /* skip_comment_multi */,
720              FALSE        /* skip_comment_single */,
721              FALSE        /* scan_comment_multi */,
722              TRUE         /* scan_identifier */,
723              TRUE         /* scan_identifier_1char */,
724              FALSE        /* scan_identifier_NULL */,
725              TRUE         /* scan_symbols */,
726              FALSE        /* scan_binary */,
727              FALSE        /* scan_octal */,
728              FALSE        /* scan_float */,
729              FALSE        /* scan_hex */,
730              FALSE        /* scan_hex_dollar */,
731              FALSE        /* scan_string_sq */,
732              FALSE        /* scan_string_dq */,
733              TRUE         /* numbers_2_int */,
734              FALSE        /* int_2_float */,
735              FALSE        /* identifier_2_string */,
736              FALSE        /* char_2_token */,
737              FALSE        /* symbol_2_token */,
738              TRUE         /* scope_0_fallback */,
739              FALSE,
740              TRUE
741      };
742
743      uzbl.scan = g_scanner_new(&scan_config);
744      while(symp->symbol_name) {
745          g_scanner_scope_add_symbol(uzbl.scan, 0,
746                          symp->symbol_name,
747                          GINT_TO_POINTER(symp->symbol_token));
748          symp++;
749      }
750 }
751
752 static gchar *
753 expand_template(const char *template, gboolean escape_markup) {
754      if(!template) return NULL;
755
756      GTokenType token = G_TOKEN_NONE;
757      GString *ret = g_string_new("");
758      char *buf=NULL;
759      int sym;
760
761      g_scanner_input_text(uzbl.scan, template, strlen(template));
762      while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
763          token = g_scanner_get_next_token(uzbl.scan);
764
765          if(token == G_TOKEN_SYMBOL) {
766              sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
767              switch(sym) {
768                  case SYM_URI:
769                      if(escape_markup) {
770                          buf = uzbl.state.uri?
771                              g_markup_printf_escaped("%s", uzbl.state.uri):g_strdup("");
772                          g_string_append(ret, buf);
773                          g_free(buf);
774                      }
775                      else
776                          g_string_append(ret, uzbl.state.uri?
777                                  uzbl.state.uri:g_strdup(""));
778                      break;
779                  case SYM_LOADPRGS:
780                      buf = itos(uzbl.gui.sbar.load_progress);
781                      g_string_append(ret, buf);
782                      g_free(buf);
783                      break;
784                  case SYM_LOADPRGSBAR:
785                      buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
786                      g_string_append(ret, buf);
787                      g_free(buf);
788                      break;
789                  case SYM_TITLE:
790                      if(escape_markup) {
791                          buf = uzbl.gui.main_title?
792                              g_markup_printf_escaped("%s", uzbl.gui.main_title):g_strdup("");
793                          g_string_append(ret, buf);
794                          g_free(buf);
795                      }
796                      else
797                          g_string_append(ret, uzbl.gui.main_title?
798                                  uzbl.gui.main_title:g_strdup(""));
799                      break;
800                  case SYM_SELECTED_URI:
801                      if(escape_markup) {
802                          buf = uzbl.state.selected_url?
803                              g_markup_printf_escaped("%s", uzbl.state.selected_url):g_strdup("");
804                          g_string_append(ret, buf);
805                          g_free(buf);
806                      }
807                      else
808                          g_string_append(ret, uzbl.state.selected_url?
809                                  uzbl.state.selected_url:g_strdup(""));
810                      break;
811                  case SYM_NAME:
812                      buf = itos(uzbl.xwin);
813                      g_string_append(ret,
814                              uzbl.state.instance_name?uzbl.state.instance_name:buf);
815                      g_free(buf);
816                      break;
817                  case SYM_KEYCMD:
818                      if(escape_markup) {
819                          buf = uzbl.state.keycmd->str?
820                              g_markup_printf_escaped("%s", uzbl.state.keycmd->str):g_strdup("");
821                          g_string_append(ret, buf);
822                          g_free(buf);
823                      }
824                      else
825                          g_string_append(ret, uzbl.state.keycmd->str?
826                                  uzbl.state.keycmd->str:g_strdup(""));
827                      break;
828                  case SYM_MODE:
829                      g_string_append(ret,
830                              uzbl.behave.insert_mode?"[I]":"[C]");
831                      break;
832                  case SYM_MSG:
833                      g_string_append(ret,
834                              uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
835                      break;
836                      /* useragent syms */
837                  case SYM_WK_MAJ:
838                      buf = itos(WEBKIT_MAJOR_VERSION);
839                      g_string_append(ret, buf);
840                      g_free(buf);
841                      break;
842                  case SYM_WK_MIN:
843                      buf = itos(WEBKIT_MINOR_VERSION);
844                      g_string_append(ret, buf);
845                      g_free(buf);
846                      break;
847                  case SYM_WK_MIC:
848                      buf = itos(WEBKIT_MICRO_VERSION);
849                      g_string_append(ret, buf);
850                      g_free(buf);
851                      break;
852                  case SYM_SYSNAME:
853                      g_string_append(ret, uzbl.state.unameinfo.sysname);
854                      break;
855                  case SYM_NODENAME:
856                      g_string_append(ret, uzbl.state.unameinfo.nodename);
857                      break;
858                  case SYM_KERNREL:
859                      g_string_append(ret, uzbl.state.unameinfo.release);
860                      break;
861                  case SYM_KERNVER:
862                      g_string_append(ret, uzbl.state.unameinfo.version);
863                      break;
864                  case SYM_ARCHSYS:
865                      g_string_append(ret, uzbl.state.unameinfo.machine);
866                      break;
867                  case SYM_ARCHUZBL:
868                      g_string_append(ret, ARCH);
869                      break;
870 #ifdef _GNU_SOURCE
871                  case SYM_DOMAINNAME:
872                      g_string_append(ret, uzbl.state.unameinfo.domainname);
873                      break;
874 #endif
875                  case SYM_COMMIT:
876                      g_string_append(ret, COMMIT);
877                      break;
878                  default:
879                      break;
880              }
881          }
882          else if(token == G_TOKEN_INT) {
883              buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
884              g_string_append(ret, buf);
885              g_free(buf);
886          }
887          else if(token == G_TOKEN_IDENTIFIER) {
888              g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
889          }
890          else if(token == G_TOKEN_CHAR) {
891              g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
892          }
893      }
894
895      return g_string_free(ret, FALSE);
896 }
897 /* --End Statusbar functions-- */
898
899 static void
900 sharg_append(GArray *a, const gchar *str) {
901     const gchar *s = (str ? str : "");
902     g_array_append_val(a, s);
903 }
904
905 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
906 static gboolean
907 run_command (const gchar *command, const guint npre, const gchar **args,
908              const gboolean sync, char **_stdout) {
909    //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
910     GError *err = NULL;
911     
912     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
913     gchar *pid = itos(getpid());
914     gchar *xwin = itos(uzbl.xwin);
915     guint i;
916     sharg_append(a, command);
917     for (i = 0; i < npre; i++) /* add n args before the default vars */
918         sharg_append(a, args[i]);
919     sharg_append(a, uzbl.state.config_file);
920     sharg_append(a, pid);
921     sharg_append(a, xwin);
922     sharg_append(a, uzbl.comm.fifo_path);
923     sharg_append(a, uzbl.comm.socket_path);
924     sharg_append(a, uzbl.state.uri);
925     sharg_append(a, uzbl.gui.main_title);
926
927     for (i = npre; i < g_strv_length((gchar**)args); i++)
928         sharg_append(a, args[i]);
929     
930     gboolean result;
931     if (sync) {
932         if (*_stdout) *_stdout = strfree(*_stdout);
933         
934         result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
935                               NULL, NULL, _stdout, NULL, NULL, &err);
936     } else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
937                                   NULL, NULL, NULL, &err);
938
939     if (uzbl.state.verbose) {
940         GString *s = g_string_new("spawned:");
941         for (i = 0; i < (a->len); i++) {
942             gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
943             g_string_append_printf(s, " %s", qarg);
944             g_free (qarg);
945         }
946         g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
947         printf("%s\n", s->str);
948         g_string_free(s, TRUE);
949     }
950     if (err) {
951         g_printerr("error on run_command: %s\n", err->message);
952         g_error_free (err);
953     }
954     g_free (pid);
955     g_free (xwin);
956     g_array_free (a, TRUE);
957     return result;
958 }
959
960 static gchar**
961 split_quoted(const gchar* src, const gboolean unquote) {
962     /* split on unquoted space, return array of strings;
963        remove a layer of quotes and backslashes if unquote */
964     if (!src) return NULL;
965     
966     gboolean dq = FALSE;
967     gboolean sq = FALSE;
968     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
969     GString *s = g_string_new ("");
970     const gchar *p;
971     gchar **ret;
972     gchar *dup;
973     for (p = src; *p != '\0'; p++) {
974         if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
975         else if (*p == '\\') { g_string_append_c(s, *p++);
976                                g_string_append_c(s, *p); }
977         else if ((*p == '"') && unquote && !sq) dq = !dq;
978         else if (*p == '"' && !sq) { g_string_append_c(s, *p);
979                                      dq = !dq; }
980         else if ((*p == '\'') && unquote && !dq) sq = !sq;
981         else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
982                                       sq = ! sq; }
983         else if ((*p == ' ') && !dq && !sq) {
984             dup = g_strdup(s->str);
985             g_array_append_val(a, dup);
986             g_string_truncate(s, 0);
987         } else g_string_append_c(s, *p);
988     }
989     dup = g_strdup(s->str);
990     g_array_append_val(a, dup);
991     ret = (gchar**)a->data;
992     g_array_free (a, FALSE);
993     g_string_free (s, TRUE);
994     return ret;
995 }
996
997 static void
998 spawn(WebKitWebView *web_view, GArray *argv) {
999     (void)web_view;
1000     //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
1001     if (argv_idx(argv, 0))
1002         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))), FALSE, NULL);
1003 }
1004
1005 static void
1006 spawn_sync(WebKitWebView *web_view, GArray *argv) {
1007     (void)web_view;
1008     
1009     if (argv_idx(argv, 0))
1010         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))),
1011                     TRUE, &uzbl.comm.sync_stdout);
1012 }
1013
1014 static void
1015 spawn_sh(WebKitWebView *web_view, GArray *argv) {
1016     (void)web_view;
1017     if (!uzbl.behave.shell_cmd) {
1018         g_printerr ("spawn_sh: shell_cmd is not set!\n");
1019         return;
1020     }
1021     
1022     guint i;
1023     gchar *spacer = g_strdup("");
1024     g_array_insert_val(argv, 1, spacer);
1025     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1026
1027     for (i = 1; i < g_strv_length(cmd); i++)
1028         g_array_prepend_val(argv, cmd[i]);
1029
1030     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL);
1031     g_free (spacer);
1032     g_strfreev (cmd);
1033 }
1034
1035 static void
1036 spawn_sh_sync(WebKitWebView *web_view, GArray *argv) {
1037     (void)web_view;
1038     if (!uzbl.behave.shell_cmd) {
1039         g_printerr ("spawn_sh_sync: shell_cmd is not set!\n");
1040         return;
1041     }
1042     
1043     guint i;
1044     gchar *spacer = g_strdup("");
1045     g_array_insert_val(argv, 1, spacer);
1046     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1047
1048     for (i = 1; i < g_strv_length(cmd); i++)
1049         g_array_prepend_val(argv, cmd[i]);
1050
1051     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data,
1052                          TRUE, &uzbl.comm.sync_stdout);
1053     g_free (spacer);
1054     g_strfreev (cmd);
1055 }
1056
1057 static void
1058 parse_command(const char *cmd, const char *param) {
1059     Command *c;
1060
1061     if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
1062
1063             guint i;
1064             gchar **par = split_quoted(param, TRUE);
1065             GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1066
1067             if (c[1] == NOSPLIT) { /* don't split */
1068                 sharg_append(a, param);
1069             } else if (par) {
1070                 for (i = 0; i < g_strv_length(par); i++)
1071                     sharg_append(a, par[i]);
1072             }
1073             c[0](uzbl.gui.web_view, a);
1074             g_strfreev (par);
1075             g_array_free (a, TRUE);
1076
1077     } else
1078         g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
1079 }
1080
1081 /* command parser */
1082 static void
1083 setup_regex() {
1084     uzbl.comm.get_regex  = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
1085             G_REGEX_OPTIMIZE, 0, NULL);
1086     uzbl.comm.set_regex  = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
1087             G_REGEX_OPTIMIZE, 0, NULL);
1088     uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
1089             G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
1090     uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
1091             G_REGEX_OPTIMIZE, 0, NULL);
1092     uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
1093             G_REGEX_OPTIMIZE, 0, NULL);
1094 }
1095
1096 static gboolean
1097 get_var_value(gchar *name) {
1098     uzbl_cmdprop *c;
1099
1100     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1101         if(c->type == TYPE_STR)
1102             printf("VAR: %s VALUE: %s\n", name, (char *)*c->ptr);
1103         else if(c->type == TYPE_INT)
1104             printf("VAR: %s VALUE: %d\n", name, (int)*c->ptr);
1105     }
1106     return TRUE;
1107 }
1108
1109 static void
1110 set_proxy_url() {
1111     SoupURI *suri;
1112
1113     if(*uzbl.net.proxy_url == ' '
1114        || uzbl.net.proxy_url == NULL) {
1115         soup_session_remove_feature_by_type(uzbl.net.soup_session,
1116                 (GType) SOUP_SESSION_PROXY_URI);
1117     }
1118     else {
1119         suri = soup_uri_new(uzbl.net.proxy_url);
1120         g_object_set(G_OBJECT(uzbl.net.soup_session),
1121                 SOUP_SESSION_PROXY_URI,
1122                 suri, NULL);
1123         soup_uri_free(suri);
1124     }
1125     return;
1126 }
1127
1128 static void
1129 cmd_load_uri() {
1130     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1131     g_array_append_val (a, uzbl.state.uri);
1132     load_uri(uzbl.gui.web_view, a);
1133     g_array_free (a, TRUE);
1134 }
1135
1136 static void 
1137 cmd_always_insert_mode() {
1138     uzbl.behave.insert_mode =
1139         uzbl.behave.always_insert_mode ?  TRUE : FALSE;
1140     update_title();
1141 }
1142
1143 static void
1144 cmd_max_conns() {
1145     g_object_set(G_OBJECT(uzbl.net.soup_session),
1146             SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1147 }
1148
1149 static void
1150 cmd_max_conns_host() {
1151     g_object_set(G_OBJECT(uzbl.net.soup_session),
1152             SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1153 }
1154
1155 static void
1156 cmd_http_debug() {
1157     soup_session_remove_feature
1158         (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1159     /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1160     /*g_free(uzbl.net.soup_logger);*/
1161
1162     uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1163     soup_session_add_feature(uzbl.net.soup_session,
1164             SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1165 }
1166
1167 static void
1168 cmd_font_size() {
1169     WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1170     if (uzbl.behave.font_size > 0) {
1171         g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL);
1172     }
1173     
1174     if (uzbl.behave.monospace_size > 0) {
1175         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1176                       uzbl.behave.monospace_size, NULL);
1177     } else {
1178         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1179                       uzbl.behave.font_size, NULL);
1180     }
1181 }
1182
1183 static void
1184 cmd_disable_plugins() {
1185     WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1186     g_object_set (G_OBJECT(ws), "enable-plugins", !uzbl.behave.disable_plugins, NULL);
1187 }
1188
1189 static void
1190 cmd_minimum_font_size() {
1191     WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1192     g_object_set (G_OBJECT(ws), "minimum-font-size", uzbl.behave.minimum_font_size, NULL);
1193 }
1194
1195 static void
1196 cmd_cookie_handler() {
1197     gchar **split = g_strsplit(uzbl.behave.cookie_handler, " ", 2);
1198     if ((g_strcmp0(split[0], "sh") == 0) ||
1199         (g_strcmp0(split[0], "spawn") == 0)) {
1200         g_free (uzbl.behave.cookie_handler);
1201         uzbl.behave.cookie_handler =
1202             g_strdup_printf("sync_%s %s", split[0], split[1]);
1203     }
1204     g_strfreev (split);
1205 }
1206
1207 static void
1208 cmd_fifo_dir() {
1209     uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir);
1210 }
1211
1212 static void
1213 cmd_socket_dir() {
1214     uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir);
1215 }
1216
1217 static void
1218 cmd_modkey() {
1219     int i;
1220     char *buf;
1221
1222     buf = g_utf8_strup(uzbl.behave.modkey, -1);
1223     uzbl.behave.modmask = 0;
1224
1225     if(uzbl.behave.modkey) 
1226         g_free(uzbl.behave.modkey);
1227     uzbl.behave.modkey = buf;
1228
1229     for (i = 0; modkeys[i].key != NULL; i++) {
1230         if (g_strrstr(buf, modkeys[i].key))
1231             uzbl.behave.modmask |= modkeys[i].mask;
1232     }
1233 }
1234
1235 static void
1236 cmd_useragent() {
1237     if (*uzbl.net.useragent == ' ') {
1238         g_free (uzbl.net.useragent);
1239         uzbl.net.useragent = NULL;
1240     } else {
1241         gchar *ua = expand_template(uzbl.net.useragent, FALSE);
1242         if (ua)
1243             g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1244         g_free(uzbl.net.useragent);
1245         uzbl.net.useragent = ua;
1246     }
1247 }
1248
1249 static void
1250 move_statusbar() {
1251     gtk_widget_ref(uzbl.gui.scrolled_win);
1252     gtk_widget_ref(uzbl.gui.mainbar);
1253     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1254     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1255
1256     if(uzbl.behave.status_top) {
1257         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1258         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1259     }
1260     else {
1261         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1262         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1263     }
1264     gtk_widget_unref(uzbl.gui.scrolled_win);
1265     gtk_widget_unref(uzbl.gui.mainbar);
1266     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1267     return;
1268 }
1269
1270 static gboolean
1271 set_var_value(gchar *name, gchar *val) {
1272     uzbl_cmdprop *c = NULL;
1273     char *endp = NULL;
1274
1275     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1276         /* check for the variable type */
1277         if (c->type == TYPE_STR) {
1278             g_free(*c->ptr);
1279             *c->ptr = g_strdup(val);
1280         } else if(c->type == TYPE_INT) {
1281             int *ip = GPOINTER_TO_INT(c->ptr);
1282             *ip = (int)strtoul(val, &endp, 10);
1283         }
1284
1285         /* invoke a command specific function */
1286         if(c->func) c->func();
1287     }
1288     return TRUE;
1289 }
1290
1291 static void
1292 runcmd(WebKitWebView* page, GArray *argv) {
1293     (void) page;
1294     parse_cmd_line(argv_idx(argv, 0));
1295 }
1296
1297 static void
1298 parse_cmd_line(const char *ctl_line) {
1299     gchar **tokens = NULL;
1300
1301     /* SET command */
1302     if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1303         tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1304         if(tokens[0][0] == 0) {
1305             gchar* value = parseenv(g_strdup(tokens[2]));
1306             set_var_value(tokens[1], value);
1307             g_free(value);
1308         }
1309         else
1310             printf("Error in command: %s\n", tokens[0]);
1311     }
1312     /* GET command */
1313     else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1314         tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1315         if(tokens[0][0] == 0) {
1316             get_var_value(tokens[1]);
1317         }
1318         else
1319             printf("Error in command: %s\n", tokens[0]);
1320     }
1321     /* BIND command */
1322     else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1323         tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1324         if(tokens[0][0] == 0) {
1325             gchar* value = parseenv(g_strdup(tokens[2]));
1326             add_binding(tokens[1], value);
1327             g_free(value);
1328         }
1329         else
1330             printf("Error in command: %s\n", tokens[0]);
1331     }
1332     /* ACT command */
1333     else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1334         tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1335         if(tokens[0][0] == 0) {
1336             parse_command(tokens[1], tokens[2]);
1337         }
1338         else
1339             printf("Error in command: %s\n", tokens[0]);
1340     }
1341     /* KEYCMD command */
1342     else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1343         tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1344         if(tokens[0][0] == 0) {
1345             /* should incremental commands want each individual "keystroke"
1346                sent in a loop or the whole string in one go like now? */
1347             g_string_assign(uzbl.state.keycmd, tokens[1]);
1348             run_keycmd(FALSE);
1349             if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1350                 run_keycmd(TRUE);
1351             update_title();
1352         }
1353     }
1354     /* Comments */
1355     else if(   (ctl_line[0] == '#')
1356             || (ctl_line[0] == ' ')
1357             || (ctl_line[0] == '\n'))
1358         ; /* ignore these lines */
1359     else
1360         printf("Command not understood (%s)\n", ctl_line);
1361
1362     if(tokens)
1363         g_strfreev(tokens);
1364
1365     return;
1366 }
1367
1368 static gchar*
1369 build_stream_name(int type, const gchar* dir) {
1370     char *xwin_str;
1371     State *s = &uzbl.state;
1372     gchar *str;
1373
1374     xwin_str = itos((int)uzbl.xwin);
1375     if (type == FIFO) {
1376         str = g_strdup_printf
1377             ("%s/uzbl_fifo_%s", dir,
1378              s->instance_name ? s->instance_name : xwin_str);
1379     } else if (type == SOCKET) {
1380         str = g_strdup_printf
1381             ("%s/uzbl_socket_%s", dir,
1382              s->instance_name ? s->instance_name : xwin_str );
1383     }
1384     g_free(xwin_str);
1385     return str;
1386 }
1387
1388 static gboolean
1389 control_fifo(GIOChannel *gio, GIOCondition condition) {
1390     if (uzbl.state.verbose)
1391         printf("triggered\n");
1392     gchar *ctl_line;
1393     GIOStatus ret;
1394     GError *err = NULL;
1395
1396     if (condition & G_IO_HUP)
1397         g_error ("Fifo: Read end of pipe died!\n");
1398
1399     if(!gio)
1400        g_error ("Fifo: GIOChannel broke\n");
1401
1402     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1403     if (ret == G_IO_STATUS_ERROR) {
1404         g_error ("Fifo: Error reading: %s\n", err->message);
1405         g_error_free (err);
1406     }
1407
1408     parse_cmd_line(ctl_line);
1409     g_free(ctl_line);
1410
1411     return TRUE;
1412 }
1413
1414 static gchar*
1415 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1416     if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1417         if (unlink(uzbl.comm.fifo_path) == -1)
1418             g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1419         g_free(uzbl.comm.fifo_path);
1420         uzbl.comm.fifo_path = NULL;
1421     }
1422
1423     if (*dir == ' ') { /* space unsets the variable */
1424         g_free (dir);
1425         return NULL;
1426     }
1427
1428     GIOChannel *chan = NULL;
1429     GError *error = NULL;
1430     gchar *path = build_stream_name(FIFO, dir);
1431
1432     if (!file_exists(path)) {
1433         if (mkfifo (path, 0666) == 0) {
1434             // we don't really need to write to the file, but if we open the file as 'r' we will block here, waiting for a writer to open the file.
1435             chan = g_io_channel_new_file(path, "r+", &error);
1436             if (chan) {
1437                 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1438                     if (uzbl.state.verbose)
1439                         printf ("init_fifo: created successfully as %s\n", path);
1440                     uzbl.comm.fifo_path = path;
1441                     return dir;
1442                 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1443             } else g_warning ("init_fifo: can't open: %s\n", error->message);
1444         } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1445     } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1446
1447     /* if we got this far, there was an error; cleanup */
1448     if (error) g_error_free (error);
1449     g_free(dir);
1450     g_free(path);
1451     return NULL;
1452 }
1453
1454 static gboolean
1455 control_stdin(GIOChannel *gio, GIOCondition condition) {
1456     (void) condition;
1457     gchar *ctl_line = NULL;
1458     GIOStatus ret;
1459
1460     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1461     if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1462         return FALSE;
1463
1464     parse_cmd_line(ctl_line);
1465     g_free(ctl_line);
1466
1467     return TRUE;
1468 }
1469
1470 static void
1471 create_stdin () {
1472     GIOChannel *chan = NULL;
1473     GError *error = NULL;
1474
1475     chan = g_io_channel_unix_new(fileno(stdin));
1476     if (chan) {
1477         if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1478             g_error ("Stdin: could not add watch\n");
1479         } else {
1480             if (uzbl.state.verbose)
1481                 printf ("Stdin: watch added successfully\n");
1482         }
1483     } else {
1484         g_error ("Stdin: Error while opening: %s\n", error->message);
1485     }
1486     if (error) g_error_free (error);
1487 }
1488
1489 static gboolean
1490 control_socket(GIOChannel *chan) {
1491     struct sockaddr_un remote;
1492     char buffer[512], *ctl_line;
1493     char temp[128];
1494     int sock, clientsock, n, done;
1495     unsigned int t;
1496
1497     sock = g_io_channel_unix_get_fd(chan);
1498
1499     memset (buffer, 0, sizeof (buffer));
1500
1501     t          = sizeof (remote);
1502     clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1503
1504     done = 0;
1505     do {
1506         memset (temp, 0, sizeof (temp));
1507         n = recv (clientsock, temp, 128, 0);
1508         if (n == 0) {
1509             buffer[strlen (buffer)] = '\0';
1510             done = 1;
1511         }
1512         if (!done)
1513             strcat (buffer, temp);
1514     } while (!done);
1515
1516     if (strcmp (buffer, "\n") < 0) {
1517         buffer[strlen (buffer) - 1] = '\0';
1518     } else {
1519         buffer[strlen (buffer)] = '\0';
1520     }
1521     close (clientsock);
1522     ctl_line = g_strdup(buffer);
1523     parse_cmd_line (ctl_line);
1524
1525 /*
1526    TODO: we should be able to do it with this.  but glib errors out with "Invalid argument"
1527     GError *error = NULL;
1528     gsize len;
1529     GIOStatus ret;
1530     ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1531     if (ret == G_IO_STATUS_ERROR)
1532         g_error ("Error reading: %s\n", error->message);
1533
1534     printf("Got line %s (%u bytes) \n",ctl_line, len);
1535     if(ctl_line) {
1536        parse_line(ctl_line);
1537 */
1538
1539     g_free(ctl_line);
1540     return TRUE;
1541 }
1542
1543 static gchar*
1544 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1545     if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1546         if (unlink(uzbl.comm.socket_path) == -1)
1547             g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1548         g_free(uzbl.comm.socket_path);
1549         uzbl.comm.socket_path = NULL;
1550     }
1551
1552     if (*dir == ' ') {
1553         g_free(dir);
1554         return NULL;
1555     }
1556
1557     GIOChannel *chan = NULL;
1558     int sock, len;
1559     struct sockaddr_un local;
1560     gchar *path = build_stream_name(SOCKET, dir);
1561
1562     sock = socket (AF_UNIX, SOCK_STREAM, 0);
1563
1564     local.sun_family = AF_UNIX;
1565     strcpy (local.sun_path, path);
1566     unlink (local.sun_path);
1567
1568     len = strlen (local.sun_path) + sizeof (local.sun_family);
1569     if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1570         if (uzbl.state.verbose)
1571             printf ("init_socket: opened in %s\n", path);
1572         listen (sock, 5);
1573
1574         if( (chan = g_io_channel_unix_new(sock)) ) {
1575             g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1576             uzbl.comm.socket_path = path;
1577             return dir;
1578         }
1579     } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1580
1581     /* if we got this far, there was an error; cleanup */
1582     g_free(path);
1583     g_free(dir);
1584     return NULL;
1585 }
1586
1587 /*
1588  NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1589  it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1590 */
1591 // this function may be called very early when the templates are not set (yet), hence the checks
1592 static void
1593 update_title (void) {
1594     Behaviour *b = &uzbl.behave;
1595     gchar *parsed;
1596
1597     if (b->show_status) {
1598         if (b->title_format_short) {
1599             parsed = expand_template(b->title_format_short, FALSE);
1600             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1601             g_free(parsed);
1602         }
1603         if (b->status_format) {
1604             parsed = expand_template(b->status_format, TRUE);
1605             gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1606             g_free(parsed);
1607         }
1608         if (b->status_background) {
1609             GdkColor color;
1610             gdk_color_parse (b->status_background, &color);
1611             //labels and hboxes do not draw their own background.  applying this on the window is ok as we the statusbar is the only affected widget.  (if not, we could also use GtkEventBox)
1612             gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1613         }
1614     } else {
1615         if (b->title_format_long) {
1616             parsed = expand_template(b->title_format_long, FALSE);
1617             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1618             g_free(parsed);
1619         }
1620     }
1621 }
1622
1623 static gboolean
1624 key_press_cb (GtkWidget* window, GdkEventKey* event)
1625 {
1626     //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1627
1628     (void) window;
1629
1630     if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1631         || event->keyval == GDK_Up || event->keyval == GDK_Down || event->keyval == GDK_Left || event->keyval == GDK_Right || event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
1632         return FALSE;
1633
1634     /* turn off insert mode (if always_insert_mode is not used) */
1635     if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1636         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1637         update_title();
1638         return TRUE;
1639     }
1640
1641     if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1642         return FALSE;
1643
1644     if (event->keyval == GDK_Escape) {
1645         g_string_truncate(uzbl.state.keycmd, 0);
1646         update_title();
1647         return TRUE;
1648     }
1649
1650     //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1651     if (event->keyval == GDK_Insert) {
1652         gchar * str;
1653         if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1654             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1655         } else {
1656             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1657         }
1658         if (str) {
1659             g_string_append (uzbl.state.keycmd, str);
1660             update_title ();
1661             g_free (str);
1662         }
1663         return TRUE;
1664     }
1665
1666     if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1667         g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1668         update_title();
1669     }
1670
1671     gboolean key_ret = FALSE;
1672     if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1673         key_ret = TRUE;
1674     if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1675
1676     run_keycmd(key_ret);
1677     update_title();
1678     if (key_ret) return (!uzbl.behave.insert_mode);
1679     return TRUE;
1680 }
1681
1682 static void
1683 run_keycmd(const gboolean key_ret) {
1684     /* run the keycmd immediately if it isn't incremental and doesn't take args */
1685     Action *action;
1686     if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1687         g_string_truncate(uzbl.state.keycmd, 0);
1688         parse_command(action->name, action->param);
1689         return;
1690     }
1691
1692     /* try if it's an incremental keycmd or one that takes args, and run it */
1693     GString* short_keys = g_string_new ("");
1694     GString* short_keys_inc = g_string_new ("");
1695     unsigned int i;
1696     for (i=0; i<(uzbl.state.keycmd->len); i++) {
1697         g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1698         g_string_assign(short_keys_inc, short_keys->str);
1699         g_string_append_c(short_keys, '_');
1700         g_string_append_c(short_keys_inc, '*');
1701
1702         gboolean exec_now = FALSE;
1703         if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1704             if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1705         } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1706             if (key_ret) { /* just quit the incremental command on return */
1707                 g_string_truncate(uzbl.state.keycmd, 0);
1708                 break;
1709             } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1710         }
1711
1712         if (exec_now) {
1713             GString* parampart = g_string_new (uzbl.state.keycmd->str);
1714             GString* actionname = g_string_new ("");
1715             GString* actionparam = g_string_new ("");
1716             g_string_erase (parampart, 0, i+1);
1717             if (action->name)
1718                 g_string_printf (actionname, action->name, parampart->str);
1719             if (action->param)
1720                 g_string_printf (actionparam, action->param, parampart->str);
1721             parse_command(actionname->str, actionparam->str);
1722             g_string_free (actionname, TRUE);
1723             g_string_free (actionparam, TRUE);
1724             g_string_free (parampart, TRUE);
1725             if (key_ret)
1726                 g_string_truncate(uzbl.state.keycmd, 0);
1727             break;
1728         }
1729
1730         g_string_truncate(short_keys, short_keys->len - 1);
1731     }
1732     g_string_free (short_keys, TRUE);
1733     g_string_free (short_keys_inc, TRUE);
1734 }
1735
1736 static GtkWidget*
1737 create_browser () {
1738     GUI *g = &uzbl.gui;
1739
1740     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1741     //main_window_ref = g_object_ref(scrolled_window);
1742     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_NEVER); //todo: some sort of display of position/total length. like what emacs does
1743
1744     g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1745     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1746
1747     g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1748     g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1749     g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1750     g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1751     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1752     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1753     g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1754     g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1755     g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1756     g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1757
1758     return scrolled_window;
1759 }
1760
1761 static GtkWidget*
1762 create_mainbar () {
1763     GUI *g = &uzbl.gui;
1764
1765     g->mainbar = gtk_hbox_new (FALSE, 0);
1766
1767     /* keep a reference to the bar so we can re-pack it at runtime*/
1768     //sbar_ref = g_object_ref(g->mainbar);
1769
1770     g->mainbar_label = gtk_label_new ("");
1771     gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1772     gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1773     gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1774     gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1775     gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1776     return g->mainbar;
1777 }
1778
1779 static
1780 GtkWidget* create_window () {
1781     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1782     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1783     gtk_widget_set_name (window, "Uzbl browser");
1784     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1785     g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);
1786
1787     return window;
1788 }
1789
1790 static void
1791 run_handler (const gchar *act, const gchar *args) {
1792     char **parts = g_strsplit(act, " ", 2);
1793     if (!parts) return;
1794     else if ((g_strcmp0(parts[0], "spawn") == 0)
1795              || (g_strcmp0(parts[0], "sh") == 0)
1796              || (g_strcmp0(parts[0], "sync_spawn") == 0)
1797              || (g_strcmp0(parts[0], "sync_sh") == 0)) {
1798         guint i;
1799         GString *a = g_string_new ("");
1800         char **spawnparts;
1801         spawnparts = split_quoted(parts[1], FALSE);
1802         g_string_append_printf(a, "%s", spawnparts[0]);
1803         if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1804         
1805         for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1806             g_string_append_printf(a, " %s", spawnparts[i]);
1807         parse_command(parts[0], a->str);
1808         g_string_free (a, TRUE);
1809         g_strfreev (spawnparts);
1810     } else
1811         parse_command(parts[0], parts[1]);
1812     g_strfreev (parts);
1813 }
1814
1815 static void
1816 add_binding (const gchar *key, const gchar *act) {
1817     char **parts = g_strsplit(act, " ", 2);
1818     Action *action;
1819
1820     if (!parts)
1821         return;
1822
1823     //Debug:
1824     if (uzbl.state.verbose)
1825         printf ("Binding %-10s : %s\n", key, act);
1826     action = new_action(parts[0], parts[1]);
1827
1828     g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
1829     g_strfreev(parts);
1830 }
1831
1832 static gchar*
1833 get_xdg_var (XDG_Var xdg) {
1834     const gchar* actual_value = getenv (xdg.environmental);
1835     const gchar* home         = getenv ("HOME");
1836
1837     gchar* return_value = str_replace ("~", home, actual_value);
1838
1839     if (! actual_value || strcmp (actual_value, "") == 0) {
1840         if (xdg.default_value) {
1841             return_value = str_replace ("~", home, xdg.default_value);
1842         } else {
1843             return_value = NULL;
1844         }
1845     }
1846     return return_value;
1847 }
1848
1849 static gchar*
1850 find_xdg_file (int xdg_type, char* filename) {
1851     /* xdg_type = 0 => config
1852        xdg_type = 1 => data
1853        xdg_type = 2 => cache*/
1854
1855     gchar* xdgv = get_xdg_var (XDG[xdg_type]);
1856     gchar* temporary_file = g_strconcat (xdgv, filename, NULL);
1857     g_free (xdgv);
1858
1859     gchar* temporary_string;
1860     char*  saveptr;
1861     char*  buf;
1862
1863     if (! file_exists (temporary_file) && xdg_type != 2) {
1864         buf = get_xdg_var (XDG[3 + xdg_type]);
1865         temporary_string = (char *) strtok_r (buf, ":", &saveptr);
1866         g_free(buf);
1867
1868         while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) {
1869             g_free (temporary_file);
1870             temporary_file = g_strconcat (temporary_string, filename, NULL);
1871         }
1872     }
1873     
1874     //g_free (temporary_string); - segfaults.
1875
1876     if (file_exists (temporary_file)) {
1877         return temporary_file;
1878     } else {
1879         return NULL;
1880     }
1881 }
1882 static void
1883 settings_init () {
1884     State *s = &uzbl.state;
1885     Network *n = &uzbl.net;
1886     int i;
1887     for (i = 0; default_config[i].command != NULL; i++) {
1888         parse_cmd_line(default_config[i].command);
1889     }
1890
1891     if (!s->config_file) {
1892         s->config_file = find_xdg_file (0, "/uzbl/config");
1893     }
1894
1895     if (s->config_file) {
1896         GArray* lines = read_file_by_line (s->config_file);
1897         int i = 0;
1898         gchar* line;
1899
1900         while ((line = g_array_index(lines, gchar*, i))) {
1901             parse_cmd_line (line);
1902             i ++;
1903             g_free (line);
1904         }
1905         g_array_free (lines, TRUE);
1906     } else {
1907         if (uzbl.state.verbose)
1908             printf ("No configuration file loaded.\n");
1909     }
1910
1911     g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1912 }
1913
1914 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1915     (void) session;
1916     (void) user_data;
1917     if (!uzbl.behave.cookie_handler) return;
1918
1919     soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1920     GString *s = g_string_new ("");
1921     SoupURI * soup_uri = soup_message_get_uri(msg);
1922     g_string_printf(s, "GET '%s' '%s'", soup_uri->host, soup_uri->path);
1923     run_handler(uzbl.behave.cookie_handler, s->str);
1924
1925     if(uzbl.comm.sync_stdout)
1926         soup_message_headers_replace (msg->request_headers, "Cookie", uzbl.comm.sync_stdout);
1927     //printf("stdout: %s\n", uzbl.comm.sync_stdout);   // debugging
1928     if (uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout);
1929         
1930     g_string_free(s, TRUE);
1931 }
1932
1933 static void
1934 save_cookies (SoupMessage *msg, gpointer user_data){
1935     (void) user_data;
1936     GSList *ck;
1937     char *cookie;
1938     for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1939         cookie = soup_cookie_to_set_cookie_header(ck->data);
1940         SoupURI * soup_uri = soup_message_get_uri(msg);
1941         GString *s = g_string_new ("");
1942         g_string_printf(s, "PUT '%s' '%s' '%s'", soup_uri->host, soup_uri->path, cookie);
1943         run_handler(uzbl.behave.cookie_handler, s->str);
1944         g_free (cookie);
1945         g_string_free(s, TRUE);
1946     }
1947     g_slist_free(ck);
1948 }
1949
1950 int
1951 main (int argc, char* argv[]) {
1952     gtk_init (&argc, &argv);
1953     if (!g_thread_supported ())
1954         g_thread_init (NULL);
1955     uzbl.state.executable_path = g_strdup(argv[0]);
1956     uzbl.state.selected_url = NULL;
1957     uzbl.state.searchtx = NULL;
1958
1959     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1960     g_option_context_add_main_entries (context, entries, NULL);
1961     g_option_context_add_group (context, gtk_get_option_group (TRUE));
1962     g_option_context_parse (context, &argc, &argv, NULL);
1963     g_option_context_free(context);
1964     /* initialize hash table */
1965     uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1966
1967     uzbl.net.soup_session = webkit_get_default_session();
1968     uzbl.state.keycmd = g_string_new("");
1969
1970     if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1971         fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1972     if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1973         fprintf(stderr, "uzbl: error hooking SIGINT\n");
1974
1975     if(uname(&uzbl.state.unameinfo) == -1)
1976         g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");
1977
1978     uzbl.gui.sbar.progress_s = g_strdup("=");
1979     uzbl.gui.sbar.progress_u = g_strdup("ยท");
1980     uzbl.gui.sbar.progress_w = 10;
1981
1982     setup_regex();
1983     setup_scanner();
1984     commands_hash ();
1985     make_var_to_name_hash();
1986
1987     uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1988
1989     uzbl.gui.scrolled_win = create_browser();
1990     create_mainbar();
1991
1992     /* initial packing */
1993     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1994     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1995
1996     uzbl.gui.main_window = create_window ();
1997     gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1998
1999
2000     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
2001     gtk_widget_show_all (uzbl.gui.main_window);
2002     uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
2003
2004     if (uzbl.state.verbose) {
2005         printf("Uzbl start location: %s\n", argv[0]);
2006         printf("window_id %i\n",(int) uzbl.xwin);
2007         printf("pid %i\n", getpid ());
2008         printf("name: %s\n", uzbl.state.instance_name);
2009     }
2010
2011     uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
2012     uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
2013     uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
2014     uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
2015     gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
2016
2017     settings_init ();
2018
2019     if (!uzbl.behave.show_status)
2020         gtk_widget_hide(uzbl.gui.mainbar);
2021     else
2022         update_title();
2023
2024     create_stdin();
2025
2026     if(uzbl.state.uri) {
2027         GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
2028         g_array_append_val(a, uzbl.state.uri);
2029         load_uri (uzbl.gui.web_view, a);
2030         g_array_free (a, TRUE);
2031     }
2032
2033     gtk_main ();
2034     clean_up();
2035
2036     return EXIT_SUCCESS;
2037 }
2038
2039 /* vi: set et ts=4: */