Add request address scheme to cookie_handler's arguments.
[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 #define _POSIX_SOURCE
36
37 #include <gtk/gtk.h>
38 #include <gdk/gdkx.h>
39 #include <gdk/gdkkeysyms.h>
40 #include <sys/socket.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <sys/un.h>
44 #include <sys/utsname.h>
45 #include <sys/time.h>
46 #include <webkit/webkit.h>
47 #include <libsoup/soup.h>
48 #include <JavaScriptCore/JavaScript.h>
49
50 #include <stdio.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <stdlib.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <signal.h>
57 #include "uzbl.h"
58 #include "config.h"
59
60 static Uzbl uzbl;
61
62
63
64 /* commandline arguments (set initial values for the state variables) */
65 static const
66 GOptionEntry entries[] =
67 {
68     { "uri",     'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri,
69         "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
70     { "verbose", 'v', 0, G_OPTION_ARG_NONE,   &uzbl.state.verbose,
71         "Whether to print all messages or just errors.", NULL },
72     { "name",    'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name,
73         "Name of the current instance (defaults to Xorg window id)", "NAME" },
74     { "config",  'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file,
75         "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
76     { "socket",  's', 0, G_OPTION_ARG_INT, &uzbl.state.socket_id,
77         "Socket ID", "SOCKET" },
78     { "version",  'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version,
79         "Print the version and exit", NULL },
80     { NULL,      0, 0, 0, NULL, NULL, NULL }
81 };
82
83 /* associate command names to their properties */
84 typedef const struct {
85     void **ptr;
86     int type;
87     int dump;
88     void (*func)(void);
89 } uzbl_cmdprop;
90
91 enum {TYPE_INT, TYPE_STR, TYPE_FLOAT};
92
93 /* an abbreviation to help keep the table's width humane */
94 #define PTR(var, t, d, fun) { .ptr = (void*)&(var), .type = TYPE_##t, .dump = d, .func = fun }
95
96 const struct {
97     char *name;
98     uzbl_cmdprop cp;
99 } var_name_to_ptr[] = {
100 /*    variable name         pointer to variable in code          type  dump callback function    */
101 /*  --------------------------------------------------------------------------------------- */
102     { "uri",                 PTR(uzbl.state.uri,                  STR,  1,   cmd_load_uri)},
103     { "verbose",             PTR(uzbl.state.verbose,              INT,  1,   NULL)},
104     { "mode",                PTR(uzbl.behave.mode,                INT,  0,   NULL)},
105     { "inject_html",         PTR(uzbl.behave.inject_html,         STR,  0,   cmd_inject_html)},
106     { "base_url",            PTR(uzbl.behave.base_url,            STR,  1,   NULL)},
107     { "html_endmarker",      PTR(uzbl.behave.html_endmarker,      STR,  1,   NULL)},
108     { "html_mode_timeout",   PTR(uzbl.behave.html_timeout,        INT,  1,   NULL)},
109     { "status_message",      PTR(uzbl.gui.sbar.msg,               STR,  1,   update_title)},
110     { "show_status",         PTR(uzbl.behave.show_status,         INT,  1,   cmd_set_status)},
111     { "status_top",          PTR(uzbl.behave.status_top,          INT,  1,   move_statusbar)},
112     { "status_format",       PTR(uzbl.behave.status_format,       STR,  1,   update_title)},
113     { "status_pbar_done",    PTR(uzbl.gui.sbar.progress_s,        STR,  1,   update_title)},
114     { "status_pbar_pending", PTR(uzbl.gui.sbar.progress_u,        STR,  1,   update_title)},
115     { "status_pbar_width",   PTR(uzbl.gui.sbar.progress_w,        INT,  1,   update_title)},
116     { "status_background",   PTR(uzbl.behave.status_background,   STR,  1,   update_title)},
117     { "insert_indicator",    PTR(uzbl.behave.insert_indicator,    STR,  1,   update_title)},
118     { "command_indicator",   PTR(uzbl.behave.cmd_indicator,       STR,  1,   update_title)},
119     { "title_format_long",   PTR(uzbl.behave.title_format_long,   STR,  1,   update_title)},
120     { "title_format_short",  PTR(uzbl.behave.title_format_short,  STR,  1,   update_title)},
121     { "icon",                PTR(uzbl.gui.icon,                   STR,  1,   set_icon)},
122     { "insert_mode",         PTR(uzbl.behave.insert_mode,         INT,  1,   NULL)},
123     { "always_insert_mode",  PTR(uzbl.behave.always_insert_mode,  INT,  1,   cmd_always_insert_mode)},
124     { "reset_command_mode",  PTR(uzbl.behave.reset_command_mode,  INT,  1,   NULL)},
125     { "modkey",              PTR(uzbl.behave.modkey,              STR,  1,   cmd_modkey)},
126     { "load_finish_handler", PTR(uzbl.behave.load_finish_handler, STR,  1,   NULL)},
127     { "load_start_handler",  PTR(uzbl.behave.load_start_handler,  STR,  1,   NULL)},
128     { "load_commit_handler", PTR(uzbl.behave.load_commit_handler, STR,  1,   NULL)},
129     { "history_handler",     PTR(uzbl.behave.history_handler,     STR,  1,   NULL)},
130     { "download_handler",    PTR(uzbl.behave.download_handler,    STR,  1,   NULL)},
131     { "cookie_handler",      PTR(uzbl.behave.cookie_handler,      STR,  1,   cmd_cookie_handler)},
132     { "new_window",          PTR(uzbl.behave.new_window,          STR,  1,   cmd_new_window)},
133     { "fifo_dir",            PTR(uzbl.behave.fifo_dir,            STR,  1,   cmd_fifo_dir)},
134     { "socket_dir",          PTR(uzbl.behave.socket_dir,          STR,  1,   cmd_socket_dir)},
135     { "http_debug",          PTR(uzbl.behave.http_debug,          INT,  1,   cmd_http_debug)},
136     { "shell_cmd",           PTR(uzbl.behave.shell_cmd,           STR,  1,   NULL)},
137     { "proxy_url",           PTR(uzbl.net.proxy_url,              STR,  1,   set_proxy_url)},
138     { "max_conns",           PTR(uzbl.net.max_conns,              INT,  1,   cmd_max_conns)},
139     { "max_conns_host",      PTR(uzbl.net.max_conns_host,         INT,  1,   cmd_max_conns_host)},
140     { "useragent",           PTR(uzbl.net.useragent,              STR,  1,   cmd_useragent)},
141     /* exported WebKitWebSettings properties */
142     { "zoom_level",          PTR(uzbl.behave.zoom_level,          FLOAT,1,   cmd_zoom_level)},
143     { "font_size",           PTR(uzbl.behave.font_size,           INT,  1,   cmd_font_size)},
144     { "monospace_size",      PTR(uzbl.behave.monospace_size,      INT,  1,   cmd_font_size)},
145     { "minimum_font_size",   PTR(uzbl.behave.minimum_font_size,   INT,  1,   cmd_minimum_font_size)},
146     { "disable_plugins",     PTR(uzbl.behave.disable_plugins,     INT,  1,   cmd_disable_plugins)},
147     { "disable_scripts",     PTR(uzbl.behave.disable_scripts,     INT,  1,   cmd_disable_scripts)},
148     { "autoload_images",     PTR(uzbl.behave.autoload_img,        INT,  1,   cmd_autoload_img)},
149     { "autoshrink_images",   PTR(uzbl.behave.autoshrink_img,      INT,  1,   cmd_autoshrink_img)},
150     { "enable_spellcheck",   PTR(uzbl.behave.enable_spellcheck,   INT,  1,   cmd_enable_spellcheck)},
151     { "enable_private",      PTR(uzbl.behave.enable_private,      INT,  1,   cmd_enable_private)},
152     { "print_backgrounds",   PTR(uzbl.behave.print_bg,            INT,  1,   cmd_print_bg)},
153     { "stylesheet_uri",      PTR(uzbl.behave.style_uri,           STR,  1,   cmd_style_uri)},
154     { "resizable_text_areas",PTR(uzbl.behave.resizable_txt,       INT,  1,   cmd_resizable_txt)},
155     { "default_encoding",    PTR(uzbl.behave.default_encoding,    STR,  1,   cmd_default_encoding)},
156     { "enforce_96_dpi",      PTR(uzbl.behave.enforce_96dpi,       INT,  1,   cmd_enforce_96dpi)},
157     { "caret_browsing",      PTR(uzbl.behave.caret_browsing,      INT,  1,   cmd_caret_browsing)},
158
159     { NULL,                  {.ptr = NULL, .type = TYPE_INT, .dump = 0, .func = NULL}}
160 }, *n2v_p = var_name_to_ptr;
161
162 const struct {
163     char *key;
164     guint mask;
165 } modkeys[] = {
166     { "SHIFT",   GDK_SHIFT_MASK   }, // shift
167     { "LOCK",    GDK_LOCK_MASK    }, // capslock or shiftlock, depending on xserver's modmappings
168     { "CONTROL", GDK_CONTROL_MASK }, // control
169     { "MOD1",    GDK_MOD1_MASK    }, // 4th mod - normally alt but depends on modmappings
170     { "MOD2",    GDK_MOD2_MASK    }, // 5th mod
171     { "MOD3",    GDK_MOD3_MASK    }, // 6th mod
172     { "MOD4",    GDK_MOD4_MASK    }, // 7th mod
173     { "MOD5",    GDK_MOD5_MASK    }, // 8th mod
174     { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
175     { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
176     { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
177     { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
178     { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
179     { "SUPER",   GDK_SUPER_MASK   }, // super (since 2.10)
180     { "HYPER",   GDK_HYPER_MASK   }, // hyper (since 2.10)
181     { "META",    GDK_META_MASK    }, // meta (since 2.10)
182     { NULL,      0                }
183 };
184
185
186 /* construct a hash from the var_name_to_ptr array for quick access */
187 static void
188 make_var_to_name_hash() {
189     uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
190     while(n2v_p->name) {
191         g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
192         n2v_p++;
193     }
194 }
195
196 /* --- UTILITY FUNCTIONS --- */
197
198 enum {EXP_ERR, EXP_SIMPLE_VAR, EXP_BRACED_VAR, EXP_EXPR, EXP_JS};
199 static guint
200 get_exp_type(gchar *s) {
201     /* variables */
202     if(*(s+1) == '(')
203         return EXP_EXPR;
204     else if(*(s+1) == '{')
205         return EXP_BRACED_VAR;
206     else if(*(s+1) == '<')
207         return EXP_JS;
208     else
209         return EXP_SIMPLE_VAR;
210
211 return EXP_ERR;
212 }
213
214 /*
215  * recurse == 1: don't expand '@(command)@'
216  * recurse == 2: don't expand '@<java script>@'
217  */
218 static gchar *
219 expand(char *s, guint recurse) {
220     uzbl_cmdprop *c;
221     guint etype;
222     char upto = ' ';
223     char *end_simple_var = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½";
224     char str_end[2];
225     char ret[4096];
226     char *vend;
227     GError *err = NULL;
228     gchar *cmd_stdout = NULL;
229     gchar *mycmd = NULL;
230     GString *buf = g_string_new("");
231     GString *js_ret = g_string_new("");
232
233     while(*s) {
234         switch(*s) {
235             case '\\':
236                 g_string_append_c(buf, *++s);
237                 s++;
238                 break;
239
240             case '@':
241                 etype = get_exp_type(s);
242                 s++;
243
244                 switch(etype) {
245                     case EXP_SIMPLE_VAR:
246                         if( (vend = strpbrk(s, end_simple_var)) ||
247                             (vend = strchr(s, '\0')) ) {
248                             strncpy(ret, s, vend-s);
249                             ret[vend-s] = '\0';
250                         }
251                         break;
252                     case EXP_BRACED_VAR:
253                         s++; upto = '}';
254                         if( (vend = strchr(s, upto)) ||
255                             (vend = strchr(s, '\0')) ) {
256                             strncpy(ret, s, vend-s);
257                             ret[vend-s] = '\0';
258                         }
259                         break;
260                     case EXP_EXPR:
261                         s++;
262                         strcpy(str_end, ")@");
263                         str_end[2] = '\0';
264                         if( (vend = strstr(s, str_end)) ||
265                             (vend = strchr(s, '\0')) ) {
266                             strncpy(ret, s, vend-s);
267                             ret[vend-s] = '\0';
268                         }
269                         break;
270                     case EXP_JS:
271                         s++;
272                         strcpy(str_end, ">@");
273                         str_end[2] = '\0';
274                         if( (vend = strstr(s, str_end)) ||
275                             (vend = strchr(s, '\0')) ) {
276                             strncpy(ret, s, vend-s);
277                             ret[vend-s] = '\0';
278                         }
279                         break;
280                 }
281
282                 if(etype == EXP_SIMPLE_VAR ||
283                    etype == EXP_BRACED_VAR) {
284                     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, ret)) ) {
285                         if(c->type == TYPE_STR)
286                             g_string_append(buf, (gchar *)*c->ptr);
287                         else if(c->type == TYPE_INT) {
288                             char *b = itos((int)*c->ptr);
289                             g_string_append(buf, b);
290                             g_free(b);
291                         }
292                     }
293                     if(etype == EXP_SIMPLE_VAR)
294                         s = vend;
295                     else
296                         s = vend+1;
297                 }
298                 else if(recurse != 1 &&
299                         etype == EXP_EXPR) {
300                     mycmd = expand(ret, 1);
301                     g_spawn_command_line_sync(mycmd, &cmd_stdout, NULL, NULL, &err);
302                     g_free(mycmd);
303
304                     if (err) {
305                         g_printerr("error on running command: %s\n", err->message);
306                         g_error_free (err);
307                     }
308                     else if (*cmd_stdout) {
309                         g_string_append(buf, cmd_stdout);
310                         g_free(cmd_stdout);
311                     }
312                     s = vend+2;
313                 }
314                 else if(recurse != 2 &&
315                         etype == EXP_JS) {
316                     mycmd = expand(ret, 2);
317                     eval_js(uzbl.gui.web_view, mycmd, js_ret);
318                     g_free(mycmd);
319
320                     if(js_ret->str) {
321                         g_string_append(buf, js_ret->str);
322                         g_string_free(js_ret, TRUE);
323                         js_ret = g_string_new("");
324                     }
325                     s = vend+2;
326                 }
327                 break;
328
329             default:
330                 g_string_append_c(buf, *s);
331                 s++;
332                 break;
333         }
334     }
335     g_string_free(js_ret, TRUE);
336     return g_string_free(buf, FALSE);
337 }
338
339 char *
340 itos(int val) {
341     char tmp[20];
342
343     snprintf(tmp, sizeof(tmp), "%i", val);
344     return g_strdup(tmp);
345 }
346
347 static gchar*
348 strfree(gchar *str) { g_free(str); return NULL; }  // for freeing & setting to null in one go
349
350 static gchar*
351 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
352
353 static char *
354 str_replace (const char* search, const char* replace, const char* string) {
355     gchar **buf;
356     char *ret;
357
358     buf = g_strsplit (string, search, -1);
359     ret = g_strjoinv (replace, buf);
360     g_strfreev(buf); // somebody said this segfaults
361
362     return ret;
363 }
364
365 static GArray*
366 read_file_by_line (gchar *path) {
367     GIOChannel *chan = NULL;
368     gchar *readbuf = NULL;
369     gsize len;
370     GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
371     int i = 0;
372
373     chan = g_io_channel_new_file(path, "r", NULL);
374
375     if (chan) {
376         while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
377             const gchar* val = g_strdup (readbuf);
378             g_array_append_val (lines, val);
379             g_free (readbuf);
380             i ++;
381         }
382
383         g_io_channel_unref (chan);
384     } else {
385         fprintf(stderr, "File '%s' not be read.\n", path);
386     }
387
388     return lines;
389 }
390
391 static
392 gchar* parseenv (char* string) {
393     extern char** environ;
394     gchar* tmpstr = NULL;
395     int i = 0;
396
397
398     while (environ[i] != NULL) {
399         gchar** env = g_strsplit (environ[i], "=", 2);
400         gchar* envname = g_strconcat ("$", env[0], NULL);
401
402         if (g_strrstr (string, envname) != NULL) {
403             tmpstr = g_strdup(string);
404             g_free (string);
405             string = str_replace(envname, env[1], tmpstr);
406             g_free (tmpstr);
407         }
408
409         g_free (envname);
410         g_strfreev (env); // somebody said this breaks uzbl
411         i++;
412     }
413
414     return string;
415 }
416
417 static sigfunc*
418 setup_signal(int signr, sigfunc *shandler) {
419     struct sigaction nh, oh;
420
421     nh.sa_handler = shandler;
422     sigemptyset(&nh.sa_mask);
423     nh.sa_flags = 0;
424
425     if(sigaction(signr, &nh, &oh) < 0)
426         return SIG_ERR;
427
428     return NULL;
429 }
430
431 static void
432 clean_up(void) {
433     if (uzbl.behave.fifo_dir)
434         unlink (uzbl.comm.fifo_path);
435     if (uzbl.behave.socket_dir)
436         unlink (uzbl.comm.socket_path);
437
438     g_free(uzbl.state.executable_path);
439     g_string_free(uzbl.state.keycmd, TRUE);
440     g_hash_table_destroy(uzbl.bindings);
441     g_hash_table_destroy(uzbl.behave.commands);
442 }
443
444 /* used for html_mode_timeout
445  * be sure to extend this function to use
446  * more timers if needed in other places
447 */
448 static void
449 set_timeout(int seconds) {
450     struct itimerval t;
451     memset(&t, 0, sizeof t);
452
453     t.it_value.tv_sec =  seconds;
454     t.it_value.tv_usec = 0;
455     setitimer(ITIMER_REAL, &t, NULL);
456 }
457
458 /* --- SIGNAL HANDLER --- */
459
460 static void
461 catch_sigterm(int s) {
462     (void) s;
463     clean_up();
464 }
465
466 static void
467 catch_sigint(int s) {
468     (void) s;
469     clean_up();
470     exit(EXIT_SUCCESS);
471 }
472
473 static void
474 catch_alrm(int s) {
475     (void) s;
476
477     set_var_value("mode", "0");
478     render_html();
479 }
480
481
482 /* --- CALLBACKS --- */
483
484 static gboolean
485 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
486     (void) web_view;
487     (void) frame;
488     (void) navigation_action;
489     (void) policy_decision;
490     (void) user_data;
491     const gchar* uri = webkit_network_request_get_uri (request);
492     if (uzbl.state.verbose)
493         printf("New window requested -> %s \n", uri);
494     webkit_web_policy_decision_use(policy_decision);
495     return TRUE;
496 }
497
498 static gboolean
499 mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
500     (void) frame;
501     (void) request;
502     (void) user_data;
503
504     /* If we can display it, let's display it... */
505     if (webkit_web_view_can_show_mime_type (web_view, mime_type)) {
506         webkit_web_policy_decision_use (policy_decision);
507         return TRUE;
508     }
509
510     /* ...everything we can't displayed is downloaded */
511     webkit_web_policy_decision_download (policy_decision);
512     return TRUE;
513 }
514
515 WebKitWebView*
516 create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer user_data) {
517     (void) web_view;
518     (void) frame;
519     (void) user_data;
520     if (uzbl.state.selected_url != NULL) {
521         if (uzbl.state.verbose)
522             printf("\nNew web view -> %s\n",uzbl.state.selected_url);
523         new_window_load_uri(uzbl.state.selected_url);
524     } else {
525         if (uzbl.state.verbose)
526             printf("New web view -> %s\n","Nothing to open, exiting");
527     }
528     return (NULL);
529 }
530
531 static gboolean
532 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
533     (void) web_view;
534     (void) user_data;
535     if (uzbl.behave.download_handler) {
536         const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
537         if (uzbl.state.verbose)
538             printf("Download -> %s\n",uri);
539         /* if urls not escaped, we may have to escape and quote uri before this call */
540         run_handler(uzbl.behave.download_handler, uri);
541     }
542     return (FALSE);
543 }
544
545 /* scroll a bar in a given direction */
546 static void
547 scroll (GtkAdjustment* bar, GArray *argv) {
548     gchar *end;
549     gdouble max_value;
550
551     gdouble page_size = gtk_adjustment_get_page_size(bar);
552     gdouble value = gtk_adjustment_get_value(bar);
553     gdouble amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
554
555     if (*end == '%')
556         value += page_size * amount * 0.01;
557     else
558         value += amount;
559
560     max_value = gtk_adjustment_get_upper(bar) - page_size;
561
562     if (value > max_value)
563         value = max_value; /* don't scroll past the end of the page */
564
565     gtk_adjustment_set_value (bar, value);
566 }
567
568 static void
569 scroll_begin(WebKitWebView* page, GArray *argv, GString *result) {
570     (void) page; (void) argv; (void) result;
571     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
572 }
573
574 static void
575 scroll_end(WebKitWebView* page, GArray *argv, GString *result) {
576     (void) page; (void) argv; (void) result;
577     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
578                               gtk_adjustment_get_page_size(uzbl.gui.bar_v));
579 }
580
581 static void
582 scroll_vert(WebKitWebView* page, GArray *argv, GString *result) {
583     (void) page; (void) result;
584     scroll(uzbl.gui.bar_v, argv);
585 }
586
587 static void
588 scroll_horz(WebKitWebView* page, GArray *argv, GString *result) {
589     (void) page; (void) result;
590     scroll(uzbl.gui.bar_h, argv);
591 }
592
593 static void
594 cmd_set_status() {
595     if (!uzbl.behave.show_status) {
596         gtk_widget_hide(uzbl.gui.mainbar);
597     } else {
598         gtk_widget_show(uzbl.gui.mainbar);
599     }
600     update_title();
601 }
602
603 static void
604 toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result) {
605     (void)page;
606     (void)argv;
607     (void)result;
608
609     webkit_web_view_set_full_content_zoom (page, !webkit_web_view_get_full_content_zoom (page));
610 }
611
612 static void
613 toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result) {
614     (void)page;
615     (void)argv;
616     (void)result;
617
618     if (uzbl.behave.show_status) {
619         gtk_widget_hide(uzbl.gui.mainbar);
620     } else {
621         gtk_widget_show(uzbl.gui.mainbar);
622     }
623     uzbl.behave.show_status = !uzbl.behave.show_status;
624     update_title();
625 }
626
627 static void
628 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
629     (void) page;
630     (void) title;
631     (void) data;
632     //Set selected_url state variable
633     g_free(uzbl.state.selected_url);
634     uzbl.state.selected_url = NULL;
635     if (link) {
636         uzbl.state.selected_url = g_strdup(link);
637     }
638     update_title();
639 }
640
641 static void
642 title_change_cb (WebKitWebView* web_view, GParamSpec param_spec) {
643     (void) web_view;
644     (void) param_spec;
645     const gchar *title = webkit_web_view_get_title(web_view);
646     if (uzbl.gui.main_title)
647         g_free (uzbl.gui.main_title);
648     uzbl.gui.main_title = title ? g_strdup (title) : g_strdup ("(no title)");
649     update_title();
650 }
651
652 static void
653 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
654     (void) page;
655     (void) data;
656     uzbl.gui.sbar.load_progress = progress;
657     update_title();
658 }
659
660 static void
661 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
662     (void) page;
663     (void) frame;
664     (void) data;
665     if (uzbl.behave.load_finish_handler)
666         run_handler(uzbl.behave.load_finish_handler, "");
667 }
668
669 static void
670 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
671     (void) page;
672     (void) frame;
673     (void) data;
674     uzbl.gui.sbar.load_progress = 0;
675     g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
676     if (uzbl.behave.load_start_handler)
677         run_handler(uzbl.behave.load_start_handler, "");
678 }
679
680 static void
681 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
682     (void) page;
683     (void) data;
684     g_free (uzbl.state.uri);
685     GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
686     uzbl.state.uri = g_string_free (newuri, FALSE);
687     if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
688         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
689         update_title();
690     }
691     if (uzbl.behave.load_commit_handler)
692         run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
693 }
694
695 static void
696 destroy_cb (GtkWidget* widget, gpointer data) {
697     (void) widget;
698     (void) data;
699     gtk_main_quit ();
700 }
701
702 static void
703 log_history_cb () {
704    if (uzbl.behave.history_handler) {
705        time_t rawtime;
706        struct tm * timeinfo;
707        char date [80];
708        time ( &rawtime );
709        timeinfo = localtime ( &rawtime );
710        strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
711        run_handler(uzbl.behave.history_handler, date);
712    }
713 }
714
715
716 /* VIEW funcs (little webkit wrappers) */
717 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv, GString *result){(void)argv; (void)result; webkit_web_view_##name(page);}
718 VIEWFUNC(reload)
719 VIEWFUNC(reload_bypass_cache)
720 VIEWFUNC(stop_loading)
721 VIEWFUNC(zoom_in)
722 VIEWFUNC(zoom_out)
723 VIEWFUNC(go_back)
724 VIEWFUNC(go_forward)
725 #undef VIEWFUNC
726
727 /* -- command to callback/function map for things we cannot attach to any signals */
728 static struct {char *key; CommandInfo value;} cmdlist[] =
729 {   /* key                   function      no_split      */
730     { "back",               {view_go_back, 0}              },
731     { "forward",            {view_go_forward, 0}           },
732     { "scroll_vert",        {scroll_vert, 0}               },
733     { "scroll_horz",        {scroll_horz, 0}               },
734     { "scroll_begin",       {scroll_begin, 0}              },
735     { "scroll_end",         {scroll_end, 0}                },
736     { "reload",             {view_reload, 0},              },
737     { "reload_ign_cache",   {view_reload_bypass_cache, 0}  },
738     { "stop",               {view_stop_loading, 0},        },
739     { "zoom_in",            {view_zoom_in, 0},             }, //Can crash (when max zoom reached?).
740     { "zoom_out",           {view_zoom_out, 0},            },
741     { "toggle_zoom_type",   {toggle_zoom_type, 0},         },
742     { "uri",                {load_uri, TRUE}               },
743     { "js",                 {run_js, TRUE}                 },
744     { "script",             {run_external_js, 0}           },
745     { "toggle_status",      {toggle_status_cb, 0}          },
746     { "spawn",              {spawn, 0}                     },
747     { "sync_spawn",         {spawn_sync, 0}                }, // needed for cookie handler
748     { "sh",                 {spawn_sh, 0}                  },
749     { "sync_sh",            {spawn_sh_sync, 0}             }, // needed for cookie handler
750     { "exit",               {close_uzbl, 0}                },
751     { "search",             {search_forward_text, TRUE}    },
752     { "search_reverse",     {search_reverse_text, TRUE}    },
753     { "dehilight",          {dehilight, 0}                 },
754     { "toggle_insert_mode", {toggle_insert_mode, 0}        },
755     { "set",                {set_var, TRUE}                },
756   //{ "get",                {get_var, TRUE}                },
757     { "bind",               {act_bind, TRUE}               },
758     { "dump_config",        {act_dump_config, 0}           },
759     { "keycmd",             {keycmd, TRUE}                 },
760     { "keycmd_nl",          {keycmd_nl, TRUE}              },
761     { "keycmd_bs",          {keycmd_bs, 0}                 },
762     { "chain",              {chain, 0}                     },
763     { "print",              {print, TRUE}                  }
764 };
765
766 static void
767 commands_hash(void)
768 {
769     unsigned int i;
770     uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
771
772     for (i = 0; i < LENGTH(cmdlist); i++)
773         g_hash_table_insert(uzbl.behave.commands, cmdlist[i].key, &cmdlist[i].value);
774 }
775
776 /* -- CORE FUNCTIONS -- */
777
778 void
779 free_action(gpointer act) {
780     Action *action = (Action*)act;
781     g_free(action->name);
782     if (action->param)
783         g_free(action->param);
784     g_free(action);
785 }
786
787 Action*
788 new_action(const gchar *name, const gchar *param) {
789     Action *action = g_new(Action, 1);
790
791     action->name = g_strdup(name);
792     if (param)
793         action->param = g_strdup(param);
794     else
795         action->param = NULL;
796
797     return action;
798 }
799
800 static bool
801 file_exists (const char * filename) {
802     return (access(filename, F_OK) == 0);
803 }
804
805 static void
806 set_var(WebKitWebView *page, GArray *argv, GString *result) {
807     (void) page; (void) result;
808     gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2);
809     if (split[0] != NULL) {
810         gchar *value = parseenv(g_strdup(split[1] ? g_strchug(split[1]) : " "));
811         set_var_value(g_strstrip(split[0]), value);
812         g_free(value);
813     }
814     g_strfreev(split);
815 }
816
817 static void
818 print(WebKitWebView *page, GArray *argv, GString *result) {
819     (void) page; (void) result;
820     gchar* buf;
821
822     buf = expand(argv_idx(argv, 0), 0);
823     g_string_assign(result, buf);
824     g_free(buf);
825 }
826
827 static void
828 act_bind(WebKitWebView *page, GArray *argv, GString *result) {
829     (void) page; (void) result;
830     gchar **split = g_strsplit(argv_idx(argv, 0), " = ", 2);
831     gchar *value = parseenv(g_strdup(split[1] ? g_strchug(split[1]) : " "));
832     add_binding(g_strstrip(split[0]), value);
833     g_free(value);
834     g_strfreev(split);
835 }
836
837
838 static void
839 act_dump_config() {
840     dump_config();
841 }
842
843 static void
844 toggle_insert_mode(WebKitWebView *page, GArray *argv, GString *result) {
845     (void) page; (void) result;
846
847     if (argv_idx(argv, 0)) {
848         if (strcmp (argv_idx(argv, 0), "0") == 0) {
849             uzbl.behave.insert_mode = FALSE;
850         } else {
851             uzbl.behave.insert_mode = TRUE;
852         }
853     } else {
854         uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
855     }
856
857     update_title();
858 }
859
860 static void
861 load_uri (WebKitWebView *web_view, GArray *argv, GString *result) {
862     (void) result;
863
864     if (argv_idx(argv, 0)) {
865         GString* newuri = g_string_new (argv_idx(argv, 0));
866         if (g_strstr_len (argv_idx(argv, 0), 11, "javascript:") != NULL) {
867             run_js(web_view, argv, NULL);
868             return;
869         }
870         if (g_strrstr (argv_idx(argv, 0), "://") == NULL && g_strstr_len (argv_idx(argv, 0), 5, "data:") == NULL)
871             g_string_prepend (newuri, "http://");
872         /* if we do handle cookies, ask our handler for them */
873         webkit_web_view_load_uri (web_view, newuri->str);
874         g_string_free (newuri, TRUE);
875     }
876 }
877
878
879 /* Javascript*/
880
881 static JSValueRef
882 js_run_command (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
883                 size_t argumentCount, const JSValueRef arguments[],
884                 JSValueRef* exception) {
885     (void) function;
886     (void) thisObject;
887     (void) exception;
888
889     JSStringRef js_result_string;
890     GString *result = g_string_new("");
891
892     if (argumentCount >= 1) {
893         JSStringRef arg = JSValueToStringCopy(ctx, arguments[0], NULL);
894         size_t arg_size = JSStringGetMaximumUTF8CStringSize(arg);
895         char ctl_line[arg_size];
896         JSStringGetUTF8CString(arg, ctl_line, arg_size);
897
898         parse_cmd_line(ctl_line, result);
899
900         JSStringRelease(arg);
901     }
902     js_result_string = JSStringCreateWithUTF8CString(result->str);
903
904     g_string_free(result, TRUE);
905
906     return JSValueMakeString(ctx, js_result_string);
907 }
908
909 static JSStaticFunction js_static_functions[] = {
910     {"run", js_run_command, kJSPropertyAttributeNone},
911 };
912
913 static void
914 js_init() {
915     /* This function creates the class and its definition, only once */
916     if (!uzbl.js.initialized) {
917         /* it would be pretty cool to make this dynamic */
918         uzbl.js.classdef = kJSClassDefinitionEmpty;
919         uzbl.js.classdef.staticFunctions = js_static_functions;
920
921         uzbl.js.classref = JSClassCreate(&uzbl.js.classdef);
922     }
923 }
924
925
926 static void
927 eval_js(WebKitWebView * web_view, gchar *script, GString *result) {
928     WebKitWebFrame *frame;
929     JSGlobalContextRef context;
930     JSObjectRef globalobject;
931     JSStringRef var_name;
932
933     JSStringRef js_script;
934     JSValueRef js_result;
935     JSStringRef js_result_string;
936     size_t js_result_size;
937
938     js_init();
939
940     frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(web_view));
941     context = webkit_web_frame_get_global_context(frame);
942     globalobject = JSContextGetGlobalObject(context);
943
944     /* uzbl javascript namespace */
945     var_name = JSStringCreateWithUTF8CString("Uzbl");
946     JSObjectSetProperty(context, globalobject, var_name,
947                         JSObjectMake(context, uzbl.js.classref, NULL),
948                         kJSClassAttributeNone, NULL);
949
950     /* evaluate the script and get return value*/
951     js_script = JSStringCreateWithUTF8CString(script);
952     js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, NULL);
953     if (js_result && !JSValueIsUndefined(context, js_result)) {
954         js_result_string = JSValueToStringCopy(context, js_result, NULL);
955         js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string);
956
957         if (js_result_size) {
958             char js_result_utf8[js_result_size];
959             JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size);
960             g_string_assign(result, js_result_utf8);
961         }
962
963         JSStringRelease(js_result_string);
964     }
965
966     /* cleanup */
967     JSObjectDeleteProperty(context, globalobject, var_name, NULL);
968
969     JSStringRelease(var_name);
970     JSStringRelease(js_script);
971 }
972
973 static void
974 run_js (WebKitWebView * web_view, GArray *argv, GString *result) {
975
976     if (argv_idx(argv, 0))
977         eval_js(web_view, argv_idx(argv, 0), result);
978 }
979
980 static void
981 run_external_js (WebKitWebView * web_view, GArray *argv, GString *result) {
982     (void) result;
983     if (argv_idx(argv, 0)) {
984         GArray* lines = read_file_by_line (argv_idx (argv, 0));
985         gchar*  js = NULL;
986         int i = 0;
987         gchar* line;
988
989         while ((line = g_array_index(lines, gchar*, i))) {
990             if (js == NULL) {
991                 js = g_strdup (line);
992             } else {
993                 gchar* newjs = g_strconcat (js, line, NULL);
994                 js = newjs;
995             }
996             i ++;
997             g_free (line);
998         }
999
1000         if (uzbl.state.verbose)
1001             printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0));
1002
1003         if (argv_idx (argv, 1)) {
1004             gchar* newjs = str_replace("%s", argv_idx (argv, 1), js);
1005             g_free (js);
1006             js = newjs;
1007         }
1008         eval_js (web_view, js, result);
1009         g_free (js);
1010         g_array_free (lines, TRUE);
1011     }
1012 }
1013
1014 static void
1015 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
1016     if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) {
1017         if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) {
1018             webkit_web_view_unmark_text_matches (page);
1019             webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0);
1020             uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
1021         }
1022     }
1023
1024     if (uzbl.state.searchtx) {
1025         if (uzbl.state.verbose)
1026             printf ("Searching: %s\n", uzbl.state.searchtx);
1027         webkit_web_view_set_highlight_text_matches (page, TRUE);
1028         webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
1029     }
1030 }
1031
1032 static void
1033 search_forward_text (WebKitWebView *page, GArray *argv, GString *result) {
1034     (void) result;
1035     search_text(page, argv, TRUE);
1036 }
1037
1038 static void
1039 search_reverse_text (WebKitWebView *page, GArray *argv, GString *result) {
1040     (void) result;
1041     search_text(page, argv, FALSE);
1042 }
1043
1044 static void
1045 dehilight (WebKitWebView *page, GArray *argv, GString *result) {
1046     (void) argv; (void) result;
1047     webkit_web_view_set_highlight_text_matches (page, FALSE);
1048 }
1049
1050
1051 static void
1052 new_window_load_uri (const gchar * uri) {
1053     if (uzbl.behave.new_window) {
1054         GString *s = g_string_new ("");
1055         g_string_printf(s, "'%s'", uri);
1056         run_handler(uzbl.behave.new_window, s->str);
1057         return;
1058     }
1059     GString* to_execute = g_string_new ("");
1060     g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
1061     int i;
1062     for (i = 0; entries[i].long_name != NULL; i++) {
1063         if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
1064             gchar** str = (gchar**)entries[i].arg_data;
1065             if (*str!=NULL) {
1066                 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
1067             }
1068         }
1069     }
1070     if (uzbl.state.verbose)
1071         printf("\n%s\n", to_execute->str);
1072     g_spawn_command_line_async (to_execute->str, NULL);
1073     g_string_free (to_execute, TRUE);
1074 }
1075
1076 static void
1077 chain (WebKitWebView *page, GArray *argv, GString *result) {
1078     (void) page; (void) result;
1079     gchar *a = NULL;
1080     gchar **parts = NULL;
1081     guint i = 0;
1082     while ((a = argv_idx(argv, i++))) {
1083         parts = g_strsplit (a, " ", 2);
1084         parse_command(parts[0], parts[1], result);
1085         g_strfreev (parts);
1086     }
1087 }
1088
1089 static void
1090 keycmd (WebKitWebView *page, GArray *argv, GString *result) {
1091     (void)page;
1092     (void)argv;
1093     (void)result;
1094     g_string_assign(uzbl.state.keycmd, argv_idx(argv, 0));
1095     run_keycmd(FALSE);
1096     update_title();
1097 }
1098
1099 static void
1100 keycmd_nl (WebKitWebView *page, GArray *argv, GString *result) {
1101     (void)page;
1102     (void)argv;
1103     (void)result;
1104     g_string_assign(uzbl.state.keycmd, argv_idx(argv, 0));
1105     run_keycmd(TRUE);
1106     update_title();
1107 }
1108
1109 static void
1110 keycmd_bs (WebKitWebView *page, GArray *argv, GString *result) {
1111     gchar *prev;
1112     (void)page;
1113     (void)argv;
1114     (void)result;
1115     prev = g_utf8_find_prev_char(uzbl.state.keycmd->str, uzbl.state.keycmd->str + uzbl.state.keycmd->len);
1116     if (prev)
1117       g_string_truncate(uzbl.state.keycmd, prev - uzbl.state.keycmd->str);
1118     update_title();
1119 }
1120
1121 static void
1122 close_uzbl (WebKitWebView *page, GArray *argv, GString *result) {
1123     (void)page;
1124     (void)argv;
1125     (void)result;
1126     gtk_main_quit ();
1127 }
1128
1129 /* --Statusbar functions-- */
1130 static char*
1131 build_progressbar_ascii(int percent) {
1132    int width=uzbl.gui.sbar.progress_w;
1133    int i;
1134    double l;
1135    GString *bar = g_string_new("");
1136
1137    l = (double)percent*((double)width/100.);
1138    l = (int)(l+.5)>=(int)l ? l+.5 : l;
1139
1140    for(i=0; i<(int)l; i++)
1141        g_string_append(bar, uzbl.gui.sbar.progress_s);
1142
1143    for(; i<width; i++)
1144        g_string_append(bar, uzbl.gui.sbar.progress_u);
1145
1146    return g_string_free(bar, FALSE);
1147 }
1148
1149 static void
1150 setup_scanner() {
1151      const GScannerConfig scan_config = {
1152              (
1153               "\t\r\n"
1154              )            /* cset_skip_characters */,
1155              (
1156               G_CSET_a_2_z
1157               "_#"
1158               G_CSET_A_2_Z
1159              )            /* cset_identifier_first */,
1160              (
1161               G_CSET_a_2_z
1162               "_0123456789"
1163               G_CSET_A_2_Z
1164               G_CSET_LATINS
1165               G_CSET_LATINC
1166              )            /* cset_identifier_nth */,
1167              ( "" )    /* cpair_comment_single */,
1168
1169              TRUE         /* case_sensitive */,
1170
1171              FALSE        /* skip_comment_multi */,
1172              FALSE        /* skip_comment_single */,
1173              FALSE        /* scan_comment_multi */,
1174              TRUE         /* scan_identifier */,
1175              TRUE         /* scan_identifier_1char */,
1176              FALSE        /* scan_identifier_NULL */,
1177              TRUE         /* scan_symbols */,
1178              FALSE        /* scan_binary */,
1179              FALSE        /* scan_octal */,
1180              FALSE        /* scan_float */,
1181              FALSE        /* scan_hex */,
1182              FALSE        /* scan_hex_dollar */,
1183              FALSE        /* scan_string_sq */,
1184              FALSE        /* scan_string_dq */,
1185              TRUE         /* numbers_2_int */,
1186              FALSE        /* int_2_float */,
1187              FALSE        /* identifier_2_string */,
1188              FALSE        /* char_2_token */,
1189              FALSE        /* symbol_2_token */,
1190              TRUE         /* scope_0_fallback */,
1191              FALSE,
1192              TRUE
1193      };
1194
1195      uzbl.scan = g_scanner_new(&scan_config);
1196      while(symp->symbol_name) {
1197          g_scanner_scope_add_symbol(uzbl.scan, 0,
1198                          symp->symbol_name,
1199                          GINT_TO_POINTER(symp->symbol_token));
1200          symp++;
1201      }
1202 }
1203
1204 static gchar *
1205 expand_template(const char *template, gboolean escape_markup) {
1206      if(!template) return NULL;
1207
1208      GTokenType token = G_TOKEN_NONE;
1209      GString *ret = g_string_new("");
1210      char *buf=NULL;
1211      int sym;
1212
1213      g_scanner_input_text(uzbl.scan, template, strlen(template));
1214      while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
1215          token = g_scanner_get_next_token(uzbl.scan);
1216
1217          if(token == G_TOKEN_SYMBOL) {
1218              sym = GPOINTER_TO_INT(g_scanner_cur_value(uzbl.scan).v_symbol);
1219              switch(sym) {
1220                  case SYM_URI:
1221                      if(escape_markup) {
1222                          buf = uzbl.state.uri?
1223                              g_markup_printf_escaped("%s", uzbl.state.uri):g_strdup("");
1224                          g_string_append(ret, buf);
1225                          g_free(buf);
1226                      }
1227                      else
1228                          g_string_append(ret, uzbl.state.uri?
1229                                  uzbl.state.uri:g_strdup(""));
1230                      break;
1231                  case SYM_LOADPRGS:
1232                      buf = itos(uzbl.gui.sbar.load_progress);
1233                      g_string_append(ret, buf);
1234                      g_free(buf);
1235                      break;
1236                  case SYM_LOADPRGSBAR:
1237                      buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
1238                      g_string_append(ret, buf);
1239                      g_free(buf);
1240                      break;
1241                  case SYM_TITLE:
1242                      if(escape_markup) {
1243                          buf = uzbl.gui.main_title?
1244                              g_markup_printf_escaped("%s", uzbl.gui.main_title):g_strdup("");
1245                          g_string_append(ret, buf);
1246                          g_free(buf);
1247                      }
1248                      else
1249                          g_string_append(ret, uzbl.gui.main_title?
1250                                  uzbl.gui.main_title:g_strdup(""));
1251                      break;
1252                  case SYM_SELECTED_URI:
1253                      if(escape_markup) {
1254                          buf = uzbl.state.selected_url?
1255                              g_markup_printf_escaped("%s", uzbl.state.selected_url):g_strdup("");
1256                          g_string_append(ret, buf);
1257                          g_free(buf);
1258                      }
1259                      else
1260                          g_string_append(ret, uzbl.state.selected_url?
1261                                  uzbl.state.selected_url:g_strdup(""));
1262                      break;
1263                  case SYM_NAME:
1264                      buf = itos(uzbl.xwin);
1265                      g_string_append(ret,
1266                              uzbl.state.instance_name?uzbl.state.instance_name:buf);
1267                      g_free(buf);
1268                      break;
1269                  case SYM_KEYCMD:
1270                      if(escape_markup) {
1271                          buf = uzbl.state.keycmd->str?
1272                              g_markup_printf_escaped("%s", uzbl.state.keycmd->str):g_strdup("");
1273                          g_string_append(ret, buf);
1274                          g_free(buf);
1275                      }
1276                      else
1277                          g_string_append(ret, uzbl.state.keycmd->str?
1278                                  uzbl.state.keycmd->str:g_strdup(""));
1279                      break;
1280                  case SYM_MODE:
1281                      g_string_append(ret,
1282                              uzbl.behave.insert_mode?
1283                              uzbl.behave.insert_indicator:uzbl.behave.cmd_indicator);
1284                      break;
1285                  case SYM_MSG:
1286                      g_string_append(ret,
1287                              uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
1288                      break;
1289                      /* useragent syms */
1290                  case SYM_WK_MAJ:
1291                      buf = itos(WEBKIT_MAJOR_VERSION);
1292                      g_string_append(ret, buf);
1293                      g_free(buf);
1294                      break;
1295                  case SYM_WK_MIN:
1296                      buf = itos(WEBKIT_MINOR_VERSION);
1297                      g_string_append(ret, buf);
1298                      g_free(buf);
1299                      break;
1300                  case SYM_WK_MIC:
1301                      buf = itos(WEBKIT_MICRO_VERSION);
1302                      g_string_append(ret, buf);
1303                      g_free(buf);
1304                      break;
1305                  case SYM_SYSNAME:
1306                      g_string_append(ret, uzbl.state.unameinfo.sysname);
1307                      break;
1308                  case SYM_NODENAME:
1309                      g_string_append(ret, uzbl.state.unameinfo.nodename);
1310                      break;
1311                  case SYM_KERNREL:
1312                      g_string_append(ret, uzbl.state.unameinfo.release);
1313                      break;
1314                  case SYM_KERNVER:
1315                      g_string_append(ret, uzbl.state.unameinfo.version);
1316                      break;
1317                  case SYM_ARCHSYS:
1318                      g_string_append(ret, uzbl.state.unameinfo.machine);
1319                      break;
1320                  case SYM_ARCHUZBL:
1321                      g_string_append(ret, ARCH);
1322                      break;
1323 #ifdef _GNU_SOURCE
1324                  case SYM_DOMAINNAME:
1325                      g_string_append(ret, uzbl.state.unameinfo.domainname);
1326                      break;
1327 #endif
1328                  case SYM_COMMIT:
1329                      g_string_append(ret, COMMIT);
1330                      break;
1331                  default:
1332                      break;
1333              }
1334          }
1335          else if(token == G_TOKEN_INT) {
1336              buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
1337              g_string_append(ret, buf);
1338              g_free(buf);
1339          }
1340          else if(token == G_TOKEN_IDENTIFIER) {
1341              g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
1342          }
1343          else if(token == G_TOKEN_CHAR) {
1344              g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
1345          }
1346      }
1347
1348      return g_string_free(ret, FALSE);
1349 }
1350 /* --End Statusbar functions-- */
1351
1352 static void
1353 sharg_append(GArray *a, const gchar *str) {
1354     const gchar *s = (str ? str : "");
1355     g_array_append_val(a, s);
1356 }
1357
1358 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
1359 static gboolean
1360 run_command (const gchar *command, const guint npre, const gchar **args,
1361              const gboolean sync, char **output_stdout) {
1362    //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
1363     GError *err = NULL;
1364
1365     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1366     gchar *pid = itos(getpid());
1367     gchar *xwin = itos(uzbl.xwin);
1368     guint i;
1369     sharg_append(a, command);
1370     for (i = 0; i < npre; i++) /* add n args before the default vars */
1371         sharg_append(a, args[i]);
1372     sharg_append(a, uzbl.state.config_file);
1373     sharg_append(a, pid);
1374     sharg_append(a, xwin);
1375     sharg_append(a, uzbl.comm.fifo_path);
1376     sharg_append(a, uzbl.comm.socket_path);
1377     sharg_append(a, uzbl.state.uri);
1378     sharg_append(a, uzbl.gui.main_title);
1379
1380     for (i = npre; i < g_strv_length((gchar**)args); i++)
1381         sharg_append(a, args[i]);
1382
1383     gboolean result;
1384     if (sync) {
1385         if (*output_stdout) *output_stdout = strfree(*output_stdout);
1386
1387         result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
1388                               NULL, NULL, output_stdout, NULL, NULL, &err);
1389     } else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
1390                                   NULL, NULL, NULL, &err);
1391
1392     if (uzbl.state.verbose) {
1393         GString *s = g_string_new("spawned:");
1394         for (i = 0; i < (a->len); i++) {
1395             gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
1396             g_string_append_printf(s, " %s", qarg);
1397             g_free (qarg);
1398         }
1399         g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
1400         printf("%s\n", s->str);
1401         g_string_free(s, TRUE);
1402         if(output_stdout) {
1403             printf("Stdout: %s\n", *output_stdout);
1404         }
1405     }
1406     if (err) {
1407         g_printerr("error on run_command: %s\n", err->message);
1408         g_error_free (err);
1409     }
1410     g_free (pid);
1411     g_free (xwin);
1412     g_array_free (a, TRUE);
1413     return result;
1414 }
1415
1416 static gchar**
1417 split_quoted(const gchar* src, const gboolean unquote) {
1418     /* split on unquoted space, return array of strings;
1419        remove a layer of quotes and backslashes if unquote */
1420     if (!src) return NULL;
1421
1422     gboolean dq = FALSE;
1423     gboolean sq = FALSE;
1424     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1425     GString *s = g_string_new ("");
1426     const gchar *p;
1427     gchar **ret;
1428     gchar *dup;
1429     for (p = src; *p != '\0'; p++) {
1430         if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
1431         else if (*p == '\\') { g_string_append_c(s, *p++);
1432                                g_string_append_c(s, *p); }
1433         else if ((*p == '"') && unquote && !sq) dq = !dq;
1434         else if (*p == '"' && !sq) { g_string_append_c(s, *p);
1435                                      dq = !dq; }
1436         else if ((*p == '\'') && unquote && !dq) sq = !sq;
1437         else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
1438                                       sq = ! sq; }
1439         else if ((*p == ' ') && !dq && !sq) {
1440             dup = g_strdup(s->str);
1441             g_array_append_val(a, dup);
1442             g_string_truncate(s, 0);
1443         } else g_string_append_c(s, *p);
1444     }
1445     dup = g_strdup(s->str);
1446     g_array_append_val(a, dup);
1447     ret = (gchar**)a->data;
1448     g_array_free (a, FALSE);
1449     g_string_free (s, TRUE);
1450     return ret;
1451 }
1452
1453 static void
1454 spawn(WebKitWebView *web_view, GArray *argv, GString *result) {
1455     (void)web_view; (void)result;
1456     //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
1457     if (argv_idx(argv, 0))
1458         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))), FALSE, NULL);
1459 }
1460
1461 static void
1462 spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) {
1463     (void)web_view; (void)result;
1464
1465     if (argv_idx(argv, 0))
1466         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))),
1467                     TRUE, &uzbl.comm.sync_stdout);
1468 }
1469
1470 static void
1471 spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result) {
1472     (void)web_view; (void)result;
1473     if (!uzbl.behave.shell_cmd) {
1474         g_printerr ("spawn_sh: shell_cmd is not set!\n");
1475         return;
1476     }
1477
1478     guint i;
1479     gchar *spacer = g_strdup("");
1480     g_array_insert_val(argv, 1, spacer);
1481     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1482
1483     for (i = 1; i < g_strv_length(cmd); i++)
1484         g_array_prepend_val(argv, cmd[i]);
1485
1486     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL);
1487     g_free (spacer);
1488     g_strfreev (cmd);
1489 }
1490
1491 static void
1492 spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) {
1493     (void)web_view; (void)result;
1494     if (!uzbl.behave.shell_cmd) {
1495         g_printerr ("spawn_sh_sync: shell_cmd is not set!\n");
1496         return;
1497     }
1498
1499     guint i;
1500     gchar *spacer = g_strdup("");
1501     g_array_insert_val(argv, 1, spacer);
1502     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1503
1504     for (i = 1; i < g_strv_length(cmd); i++)
1505         g_array_prepend_val(argv, cmd[i]);
1506
1507     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data,
1508                          TRUE, &uzbl.comm.sync_stdout);
1509     g_free (spacer);
1510     g_strfreev (cmd);
1511 }
1512
1513 static void
1514 parse_command(const char *cmd, const char *param, GString *result) {
1515     CommandInfo *c;
1516
1517     if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
1518             guint i;
1519             gchar **par = split_quoted(param, TRUE);
1520             GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1521
1522             if (c->no_split) { /* don't split */
1523                 sharg_append(a, param);
1524             } else if (par) {
1525                 for (i = 0; i < g_strv_length(par); i++)
1526                     sharg_append(a, par[i]);
1527             }
1528
1529             if (result == NULL) {
1530                 GString *result_print = g_string_new("");
1531
1532                 c->function(uzbl.gui.web_view, a, result_print);
1533                 if (result_print->len)
1534                     printf("%*s\n", result_print->len, result_print->str);
1535
1536                 g_string_free(result_print, TRUE);
1537             } else {
1538                 c->function(uzbl.gui.web_view, a, result);
1539             }
1540             g_strfreev (par);
1541             g_array_free (a, TRUE);
1542
1543     } else
1544         g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
1545 }
1546
1547 static void
1548 set_proxy_url() {
1549     SoupURI *suri;
1550
1551     if(*uzbl.net.proxy_url == ' '
1552        || uzbl.net.proxy_url == NULL) {
1553         soup_session_remove_feature_by_type(uzbl.net.soup_session,
1554                 (GType) SOUP_SESSION_PROXY_URI);
1555     }
1556     else {
1557         suri = soup_uri_new(uzbl.net.proxy_url);
1558         g_object_set(G_OBJECT(uzbl.net.soup_session),
1559                 SOUP_SESSION_PROXY_URI,
1560                 suri, NULL);
1561         soup_uri_free(suri);
1562     }
1563     return;
1564 }
1565
1566 static void
1567 set_icon() {
1568     if(file_exists(uzbl.gui.icon)) {
1569         if (uzbl.gui.main_window)
1570             gtk_window_set_icon_from_file (GTK_WINDOW (uzbl.gui.main_window), uzbl.gui.icon, NULL);
1571     } else {
1572         g_printerr ("Icon \"%s\" not found. ignoring.\n", uzbl.gui.icon);
1573     }
1574 }
1575
1576 static void
1577 cmd_load_uri() {
1578     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1579     g_array_append_val (a, uzbl.state.uri);
1580     load_uri(uzbl.gui.web_view, a, NULL);
1581     g_array_free (a, TRUE);
1582 }
1583
1584 static void
1585 cmd_always_insert_mode() {
1586     uzbl.behave.insert_mode =
1587         uzbl.behave.always_insert_mode ?  TRUE : FALSE;
1588     update_title();
1589 }
1590
1591 static void
1592 cmd_max_conns() {
1593     g_object_set(G_OBJECT(uzbl.net.soup_session),
1594             SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1595 }
1596
1597 static void
1598 cmd_max_conns_host() {
1599     g_object_set(G_OBJECT(uzbl.net.soup_session),
1600             SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1601 }
1602
1603 static void
1604 cmd_http_debug() {
1605     soup_session_remove_feature
1606         (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1607     /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1608     /*g_free(uzbl.net.soup_logger);*/
1609
1610     uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1611     soup_session_add_feature(uzbl.net.soup_session,
1612             SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1613 }
1614
1615 static WebKitWebSettings*
1616 view_settings() {
1617     return webkit_web_view_get_settings(uzbl.gui.web_view);
1618 }
1619
1620 static void
1621 cmd_font_size() {
1622     WebKitWebSettings *ws = view_settings();
1623     if (uzbl.behave.font_size > 0) {
1624         g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL);
1625     }
1626
1627     if (uzbl.behave.monospace_size > 0) {
1628         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1629                       uzbl.behave.monospace_size, NULL);
1630     } else {
1631         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1632                       uzbl.behave.font_size, NULL);
1633     }
1634 }
1635
1636 static void
1637 cmd_zoom_level() {
1638     webkit_web_view_set_zoom_level (uzbl.gui.web_view, uzbl.behave.zoom_level);
1639 }
1640
1641 static void
1642 cmd_disable_plugins() {
1643     g_object_set (G_OBJECT(view_settings()), "enable-plugins",
1644             !uzbl.behave.disable_plugins, NULL);
1645 }
1646
1647 static void
1648 cmd_disable_scripts() {
1649     g_object_set (G_OBJECT(view_settings()), "enable-scripts",
1650             !uzbl.behave.disable_scripts, NULL);
1651 }
1652
1653 static void
1654 cmd_minimum_font_size() {
1655     g_object_set (G_OBJECT(view_settings()), "minimum-font-size",
1656             uzbl.behave.minimum_font_size, NULL);
1657 }
1658 static void
1659 cmd_autoload_img() {
1660     g_object_set (G_OBJECT(view_settings()), "auto-load-images",
1661             uzbl.behave.autoload_img, NULL);
1662 }
1663
1664
1665 static void
1666 cmd_autoshrink_img() {
1667     g_object_set (G_OBJECT(view_settings()), "auto-shrink-images",
1668             uzbl.behave.autoshrink_img, NULL);
1669 }
1670
1671
1672 static void
1673 cmd_enable_spellcheck() {
1674     g_object_set (G_OBJECT(view_settings()), "enable-spell-checking",
1675             uzbl.behave.enable_spellcheck, NULL);
1676 }
1677
1678 static void
1679 cmd_enable_private() {
1680     g_object_set (G_OBJECT(view_settings()), "enable-private-browsing",
1681             uzbl.behave.enable_private, NULL);
1682 }
1683
1684 static void
1685 cmd_print_bg() {
1686     g_object_set (G_OBJECT(view_settings()), "print-backgrounds",
1687             uzbl.behave.print_bg, NULL);
1688 }
1689
1690 static void
1691 cmd_style_uri() {
1692     g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri",
1693             uzbl.behave.style_uri, NULL);
1694 }
1695
1696 static void
1697 cmd_resizable_txt() {
1698     g_object_set (G_OBJECT(view_settings()), "resizable-text-areas",
1699             uzbl.behave.resizable_txt, NULL);
1700 }
1701
1702 static void
1703 cmd_default_encoding() {
1704     g_object_set (G_OBJECT(view_settings()), "default-encoding",
1705             uzbl.behave.default_encoding, NULL);
1706 }
1707
1708 static void
1709 cmd_enforce_96dpi() {
1710     g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi",
1711             uzbl.behave.enforce_96dpi, NULL);
1712 }
1713
1714 static void
1715 cmd_caret_browsing() {
1716     g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing",
1717             uzbl.behave.caret_browsing, NULL);
1718 }
1719
1720 static void
1721 cmd_cookie_handler() {
1722     gchar **split = g_strsplit(uzbl.behave.cookie_handler, " ", 2);
1723     /* pitfall: doesn't handle chain actions; must the sync_ action manually */
1724     if ((g_strcmp0(split[0], "sh") == 0) ||
1725         (g_strcmp0(split[0], "spawn") == 0)) {
1726         g_free (uzbl.behave.cookie_handler);
1727         uzbl.behave.cookie_handler =
1728             g_strdup_printf("sync_%s %s", split[0], split[1]);
1729     }
1730     g_strfreev (split);
1731 }
1732
1733 static void
1734 cmd_new_window() {
1735     gchar **split = g_strsplit(uzbl.behave.new_window, " ", 2);
1736     /* pitfall: doesn't handle chain actions; must the sync_ action manually */
1737     if ((g_strcmp0(split[0], "sh") == 0) ||
1738         (g_strcmp0(split[0], "spawn") == 0)) {
1739         g_free (uzbl.behave.new_window);
1740         uzbl.behave.new_window =
1741             g_strdup_printf("%s %s", split[0], split[1]);
1742     }
1743     g_strfreev (split);
1744 }
1745
1746 static void
1747 cmd_fifo_dir() {
1748     uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir);
1749 }
1750
1751 static void
1752 cmd_socket_dir() {
1753     uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir);
1754 }
1755
1756 static void
1757 cmd_inject_html() {
1758     if(uzbl.behave.inject_html) {
1759         webkit_web_view_load_html_string (uzbl.gui.web_view,
1760                 uzbl.behave.inject_html, NULL);
1761     }
1762 }
1763
1764 static void
1765 cmd_modkey() {
1766     int i;
1767     char *buf;
1768
1769     buf = g_utf8_strup(uzbl.behave.modkey, -1);
1770     uzbl.behave.modmask = 0;
1771
1772     if(uzbl.behave.modkey)
1773         g_free(uzbl.behave.modkey);
1774     uzbl.behave.modkey = buf;
1775
1776     for (i = 0; modkeys[i].key != NULL; i++) {
1777         if (g_strrstr(buf, modkeys[i].key))
1778             uzbl.behave.modmask |= modkeys[i].mask;
1779     }
1780 }
1781
1782 static void
1783 cmd_useragent() {
1784     if (*uzbl.net.useragent == ' ') {
1785         g_free (uzbl.net.useragent);
1786         uzbl.net.useragent = NULL;
1787     } else {
1788         gchar *ua = expand_template(uzbl.net.useragent, FALSE);
1789         if (ua)
1790             g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1791         g_free(uzbl.net.useragent);
1792         uzbl.net.useragent = ua;
1793     }
1794 }
1795
1796 static void
1797 move_statusbar() {
1798     gtk_widget_ref(uzbl.gui.scrolled_win);
1799     gtk_widget_ref(uzbl.gui.mainbar);
1800     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1801     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1802
1803     if(uzbl.behave.status_top) {
1804         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1805         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1806     }
1807     else {
1808         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1809         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1810     }
1811     gtk_widget_unref(uzbl.gui.scrolled_win);
1812     gtk_widget_unref(uzbl.gui.mainbar);
1813     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1814     return;
1815 }
1816
1817 static gboolean
1818 set_var_value(gchar *name, gchar *val) {
1819     uzbl_cmdprop *c = NULL;
1820     char *endp = NULL;
1821     char *buf = NULL;
1822
1823     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1824         /* check for the variable type */
1825         if (c->type == TYPE_STR) {
1826             buf = expand(val, 0);
1827             g_free(*c->ptr);
1828             *c->ptr = buf;
1829         } else if(c->type == TYPE_INT) {
1830             int *ip = (int *)c->ptr;
1831             buf = expand(val, 0);
1832             *ip = (int)strtoul(buf, &endp, 10);
1833             g_free(buf);
1834         } else if (c->type == TYPE_FLOAT) {
1835             float *fp = (float *)c->ptr;
1836             buf = expand(val, 0);
1837             *fp = strtod(buf, &endp);
1838             g_free(buf);
1839         }
1840
1841         /* invoke a command specific function */
1842         if(c->func) c->func();
1843     }
1844     return TRUE;
1845 }
1846
1847 static void
1848 render_html() {
1849     Behaviour *b = &uzbl.behave;
1850
1851     if(b->html_buffer->str) {
1852         webkit_web_view_load_html_string (uzbl.gui.web_view,
1853                 b->html_buffer->str, b->base_url);
1854         g_string_free(b->html_buffer, TRUE);
1855         b->html_buffer = g_string_new("");
1856     }
1857 }
1858
1859 enum {M_CMD, M_HTML};
1860 static void
1861 parse_cmd_line(const char *ctl_line, GString *result) {
1862     Behaviour *b = &uzbl.behave;
1863     size_t len=0;
1864
1865     if(b->mode == M_HTML) {
1866         len = strlen(b->html_endmarker);
1867         /* ctl_line has trailing '\n' so we check for strlen(ctl_line)-1 */
1868         if(len == strlen(ctl_line)-1 &&
1869            !strncmp(b->html_endmarker, ctl_line, len)) {
1870             set_timeout(0);
1871             set_var_value("mode", "0");
1872             render_html();
1873             return;
1874         }
1875         else {
1876             set_timeout(b->html_timeout);
1877             g_string_append(b->html_buffer, ctl_line);
1878         }
1879     }
1880     else if((ctl_line[0] == '#') /* Comments */
1881             || (ctl_line[0] == ' ')
1882             || (ctl_line[0] == '\n'))
1883         ; /* ignore these lines */
1884     else { /* parse a command */
1885         gchar *ctlstrip;
1886         gchar **tokens = NULL;
1887         len = strlen(ctl_line);
1888
1889         if (ctl_line[len - 1] == '\n') /* strip trailing newline */
1890             ctlstrip = g_strndup(ctl_line, len - 1);
1891         else ctlstrip = g_strdup(ctl_line);
1892
1893         tokens = g_strsplit(ctlstrip, " ", 2);
1894         parse_command(tokens[0], tokens[1], result);
1895         g_free(ctlstrip);
1896         g_strfreev(tokens);
1897     }
1898 }
1899
1900 static gchar*
1901 build_stream_name(int type, const gchar* dir) {
1902     char *xwin_str = NULL;
1903     State *s = &uzbl.state;
1904     gchar *str = NULL;
1905
1906     xwin_str = itos((int)uzbl.xwin);
1907     if (type == FIFO) {
1908         str = g_strdup_printf
1909             ("%s/uzbl_fifo_%s", dir,
1910              s->instance_name ? s->instance_name : xwin_str);
1911     } else if (type == SOCKET) {
1912         str = g_strdup_printf
1913             ("%s/uzbl_socket_%s", dir,
1914              s->instance_name ? s->instance_name : xwin_str );
1915     }
1916     g_free(xwin_str);
1917     return str;
1918 }
1919
1920 static gboolean
1921 control_fifo(GIOChannel *gio, GIOCondition condition) {
1922     if (uzbl.state.verbose)
1923         printf("triggered\n");
1924     gchar *ctl_line;
1925     GIOStatus ret;
1926     GError *err = NULL;
1927
1928     if (condition & G_IO_HUP)
1929         g_error ("Fifo: Read end of pipe died!\n");
1930
1931     if(!gio)
1932        g_error ("Fifo: GIOChannel broke\n");
1933
1934     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1935     if (ret == G_IO_STATUS_ERROR) {
1936         g_error ("Fifo: Error reading: %s\n", err->message);
1937         g_error_free (err);
1938     }
1939
1940     parse_cmd_line(ctl_line, NULL);
1941     g_free(ctl_line);
1942
1943     return TRUE;
1944 }
1945
1946 static gchar*
1947 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1948     if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1949         if (unlink(uzbl.comm.fifo_path) == -1)
1950             g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1951         g_free(uzbl.comm.fifo_path);
1952         uzbl.comm.fifo_path = NULL;
1953     }
1954
1955     if (*dir == ' ') { /* space unsets the variable */
1956         g_free (dir);
1957         return NULL;
1958     }
1959
1960     GIOChannel *chan = NULL;
1961     GError *error = NULL;
1962     gchar *path = build_stream_name(FIFO, dir);
1963
1964     if (!file_exists(path)) {
1965         if (mkfifo (path, 0666) == 0) {
1966             // 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.
1967             chan = g_io_channel_new_file(path, "r+", &error);
1968             if (chan) {
1969                 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1970                     if (uzbl.state.verbose)
1971                         printf ("init_fifo: created successfully as %s\n", path);
1972                     uzbl.comm.fifo_path = path;
1973                     return dir;
1974                 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1975             } else g_warning ("init_fifo: can't open: %s\n", error->message);
1976         } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1977     } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1978
1979     /* if we got this far, there was an error; cleanup */
1980     if (error) g_error_free (error);
1981     g_free(dir);
1982     g_free(path);
1983     return NULL;
1984 }
1985
1986 static gboolean
1987 control_stdin(GIOChannel *gio, GIOCondition condition) {
1988     (void) condition;
1989     gchar *ctl_line = NULL;
1990     GIOStatus ret;
1991
1992     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1993     if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1994         return FALSE;
1995
1996     parse_cmd_line(ctl_line, NULL);
1997     g_free(ctl_line);
1998
1999     return TRUE;
2000 }
2001
2002 static void
2003 create_stdin () {
2004     GIOChannel *chan = NULL;
2005     GError *error = NULL;
2006
2007     chan = g_io_channel_unix_new(fileno(stdin));
2008     if (chan) {
2009         if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
2010             g_error ("Stdin: could not add watch\n");
2011         } else {
2012             if (uzbl.state.verbose)
2013                 printf ("Stdin: watch added successfully\n");
2014         }
2015     } else {
2016         g_error ("Stdin: Error while opening: %s\n", error->message);
2017     }
2018     if (error) g_error_free (error);
2019 }
2020
2021 static gboolean
2022 control_socket(GIOChannel *chan) {
2023     struct sockaddr_un remote;
2024     unsigned int t = sizeof(remote);
2025     int clientsock;
2026     GIOChannel *clientchan;
2027
2028     clientsock = accept (g_io_channel_unix_get_fd(chan),
2029                          (struct sockaddr *) &remote, &t);
2030
2031     if ((clientchan = g_io_channel_unix_new(clientsock))) {
2032         g_io_add_watch(clientchan, G_IO_IN|G_IO_HUP,
2033                        (GIOFunc) control_client_socket, clientchan);
2034     }
2035
2036     return TRUE;
2037 }
2038
2039 static gboolean
2040 control_client_socket(GIOChannel *clientchan) {
2041     char *ctl_line;
2042     GString *result = g_string_new("");
2043     GError *error = NULL;
2044     GIOStatus ret;
2045     gsize len;
2046
2047     ret = g_io_channel_read_line(clientchan, &ctl_line, &len, NULL, &error);
2048     if (ret == G_IO_STATUS_ERROR) {
2049         g_warning ("Error reading: %s\n", error->message);
2050         g_io_channel_shutdown(clientchan, TRUE, &error);
2051         return FALSE;
2052     } else if (ret == G_IO_STATUS_EOF) {
2053         /* shutdown and remove channel watch from main loop */
2054         g_io_channel_shutdown(clientchan, TRUE, &error);
2055         return FALSE;
2056     }
2057
2058     if (ctl_line) {
2059         parse_cmd_line (ctl_line, result);
2060         g_string_append_c(result, '\n');
2061         ret = g_io_channel_write_chars (clientchan, result->str, result->len,
2062                                         &len, &error);
2063         if (ret == G_IO_STATUS_ERROR) {
2064             g_warning ("Error writing: %s", error->message);
2065         }
2066         g_io_channel_flush(clientchan, &error);
2067     }
2068
2069     if (error) g_error_free (error);
2070     g_string_free(result, TRUE);
2071     g_free(ctl_line);
2072     return TRUE;
2073 }
2074
2075 static gchar*
2076 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
2077     if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
2078         if (unlink(uzbl.comm.socket_path) == -1)
2079             g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
2080         g_free(uzbl.comm.socket_path);
2081         uzbl.comm.socket_path = NULL;
2082     }
2083
2084     if (*dir == ' ') {
2085         g_free(dir);
2086         return NULL;
2087     }
2088
2089     GIOChannel *chan = NULL;
2090     int sock, len;
2091     struct sockaddr_un local;
2092     gchar *path = build_stream_name(SOCKET, dir);
2093
2094     sock = socket (AF_UNIX, SOCK_STREAM, 0);
2095
2096     local.sun_family = AF_UNIX;
2097     strcpy (local.sun_path, path);
2098     unlink (local.sun_path);
2099
2100     len = strlen (local.sun_path) + sizeof (local.sun_family);
2101     if (bind (sock, (struct sockaddr *) &local, len) != -1) {
2102         if (uzbl.state.verbose)
2103             printf ("init_socket: opened in %s\n", path);
2104         listen (sock, 5);
2105
2106         if( (chan = g_io_channel_unix_new(sock)) ) {
2107             g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
2108             uzbl.comm.socket_path = path;
2109             return dir;
2110         }
2111     } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
2112
2113     /* if we got this far, there was an error; cleanup */
2114     g_free(path);
2115     g_free(dir);
2116     return NULL;
2117 }
2118
2119 /*
2120  NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
2121  it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
2122 */
2123 // this function may be called very early when the templates are not set (yet), hence the checks
2124 static void
2125 update_title (void) {
2126     Behaviour *b = &uzbl.behave;
2127     gchar *parsed;
2128
2129     if (b->show_status) {
2130         if (b->title_format_short) {
2131             parsed = expand_template(b->title_format_short, FALSE);
2132             if (uzbl.gui.main_window)
2133                 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
2134             g_free(parsed);
2135         }
2136         if (b->status_format) {
2137             parsed = expand_template(b->status_format, TRUE);
2138             gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
2139             g_free(parsed);
2140         }
2141         if (b->status_background) {
2142             GdkColor color;
2143             gdk_color_parse (b->status_background, &color);
2144             //labels and hboxes do not draw their own background. applying this on the vbox/main_window is ok as the statusbar is the only affected widget. (if not, we could also use GtkEventBox)
2145             if (uzbl.gui.main_window)
2146                 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
2147             else if (uzbl.gui.plug)
2148                 gtk_widget_modify_bg (GTK_WIDGET(uzbl.gui.plug), GTK_STATE_NORMAL, &color);
2149         }
2150     } else {
2151         if (b->title_format_long) {
2152             parsed = expand_template(b->title_format_long, FALSE);
2153             if (uzbl.gui.main_window)
2154                 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
2155             g_free(parsed);
2156         }
2157     }
2158 }
2159
2160 static gboolean
2161 key_press_cb (GtkWidget* window, GdkEventKey* event)
2162 {
2163     //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
2164
2165     (void) window;
2166
2167     if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
2168         || 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)
2169         return FALSE;
2170
2171     /* turn off insert mode (if always_insert_mode is not used) */
2172     if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
2173         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
2174         update_title();
2175         return TRUE;
2176     }
2177
2178     if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
2179         return FALSE;
2180
2181     if (event->keyval == GDK_Escape) {
2182         g_string_truncate(uzbl.state.keycmd, 0);
2183         update_title();
2184         dehilight(uzbl.gui.web_view, NULL, NULL);
2185         return TRUE;
2186     }
2187
2188     //Insert without shift - insert from clipboard; Insert with shift - insert from primary
2189     if (event->keyval == GDK_Insert) {
2190         gchar * str;
2191         if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
2192             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
2193         } else {
2194             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
2195         }
2196         if (str) {
2197             g_string_append (uzbl.state.keycmd, str);
2198             update_title ();
2199             g_free (str);
2200         }
2201         return TRUE;
2202     }
2203
2204     if (event->keyval == GDK_BackSpace)
2205         keycmd_bs(NULL, NULL, NULL);
2206
2207     gboolean key_ret = FALSE;
2208     if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
2209         key_ret = TRUE;
2210     if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
2211
2212     run_keycmd(key_ret);
2213     update_title();
2214     if (key_ret) return (!uzbl.behave.insert_mode);
2215     return TRUE;
2216 }
2217
2218 static void
2219 run_keycmd(const gboolean key_ret) {
2220     /* run the keycmd immediately if it isn't incremental and doesn't take args */
2221     Action *act;
2222     if ((act = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
2223         g_string_truncate(uzbl.state.keycmd, 0);
2224         parse_command(act->name, act->param, NULL);
2225         return;
2226     }
2227
2228     /* try if it's an incremental keycmd or one that takes args, and run it */
2229     GString* short_keys = g_string_new ("");
2230     GString* short_keys_inc = g_string_new ("");
2231     guint i;
2232     for (i=0; i<(uzbl.state.keycmd->len); i++) {
2233         g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
2234         g_string_assign(short_keys_inc, short_keys->str);
2235         g_string_append_c(short_keys, '_');
2236         g_string_append_c(short_keys_inc, '*');
2237
2238         if (key_ret && (act = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
2239             /* run normal cmds only if return was pressed */
2240             exec_paramcmd(act, i);
2241             g_string_truncate(uzbl.state.keycmd, 0);
2242             break;
2243         } else if ((act = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
2244             if (key_ret)  /* just quit the incremental command on return */
2245                 g_string_truncate(uzbl.state.keycmd, 0);
2246             else exec_paramcmd(act, i); /* otherwise execute the incremental */
2247             break;
2248         }
2249
2250         g_string_truncate(short_keys, short_keys->len - 1);
2251     }
2252     g_string_free (short_keys, TRUE);
2253     g_string_free (short_keys_inc, TRUE);
2254 }
2255
2256 static void
2257 exec_paramcmd(const Action *act, const guint i) {
2258     GString *parampart = g_string_new (uzbl.state.keycmd->str);
2259     GString *actionname = g_string_new ("");
2260     GString *actionparam = g_string_new ("");
2261     g_string_erase (parampart, 0, i+1);
2262     if (act->name)
2263         g_string_printf (actionname, act->name, parampart->str);
2264     if (act->param)
2265         g_string_printf (actionparam, act->param, parampart->str);
2266     parse_command(actionname->str, actionparam->str, NULL);
2267     g_string_free(actionname, TRUE);
2268     g_string_free(actionparam, TRUE);
2269     g_string_free(parampart, TRUE);
2270 }
2271
2272
2273 static GtkWidget*
2274 create_browser () {
2275     GUI *g = &uzbl.gui;
2276
2277     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
2278     //main_window_ref = g_object_ref(scrolled_window);
2279     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
2280
2281     g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
2282     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
2283
2284     g_signal_connect (G_OBJECT (g->web_view), "notify::title", G_CALLBACK (title_change_cb), NULL);
2285     g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
2286     g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
2287     g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
2288     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
2289     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
2290     g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
2291     g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
2292     g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
2293     g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
2294     g_signal_connect (G_OBJECT (g->web_view), "mime-type-policy-decision-requested", G_CALLBACK (mime_policy_cb), g->web_view);
2295
2296     return scrolled_window;
2297 }
2298
2299 static GtkWidget*
2300 create_mainbar () {
2301     GUI *g = &uzbl.gui;
2302
2303     g->mainbar = gtk_hbox_new (FALSE, 0);
2304
2305     /* keep a reference to the bar so we can re-pack it at runtime*/
2306     //sbar_ref = g_object_ref(g->mainbar);
2307
2308     g->mainbar_label = gtk_label_new ("");
2309     gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
2310     gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
2311     gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
2312     gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
2313     gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
2314     g_signal_connect (G_OBJECT (g->mainbar), "key-press-event", G_CALLBACK (key_press_cb), NULL);
2315     return g->mainbar;
2316 }
2317
2318 static
2319 GtkWidget* create_window () {
2320     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2321     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
2322     gtk_widget_set_name (window, "Uzbl browser");
2323     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
2324     g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);
2325
2326     return window;
2327 }
2328
2329 static
2330 GtkPlug* create_plug () {
2331     GtkPlug* plug = GTK_PLUG (gtk_plug_new (uzbl.state.socket_id));
2332     g_signal_connect (G_OBJECT (plug), "destroy", G_CALLBACK (destroy_cb), NULL);
2333     g_signal_connect (G_OBJECT (plug), "key-press-event", G_CALLBACK (key_press_cb), NULL);
2334
2335     return plug;
2336 }
2337
2338
2339 static gchar**
2340 inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *newargs) {
2341     /*
2342       If actname is one that calls an external command, this function will inject
2343       newargs in front of the user-provided args in that command line.  They will
2344       come become after the body of the script (in sh) or after the name of
2345       the command to execute (in spawn).
2346       i.e. sh <body> <userargs> becomes sh <body> <ARGS> <userargs> and
2347       span <command> <userargs> becomes spawn <command> <ARGS> <userargs>.
2348
2349       The return value consist of two strings: the action (sh, ...) and its args.
2350
2351       If act is not one that calls an external command, then the given action merely
2352       gets duplicated.
2353     */
2354     GArray *rets = g_array_new(TRUE, FALSE, sizeof(gchar*));
2355     gchar *actdup = g_strdup(actname);
2356     g_array_append_val(rets, actdup);
2357
2358     if ((g_strcmp0(actname, "spawn") == 0) ||
2359         (g_strcmp0(actname, "sh") == 0) ||
2360         (g_strcmp0(actname, "sync_spawn") == 0) ||
2361         (g_strcmp0(actname, "sync_sh") == 0)) {
2362         guint i;
2363         GString *a = g_string_new("");
2364         gchar **spawnparts = split_quoted(origargs, FALSE);
2365         g_string_append_printf(a, "%s", spawnparts[0]); /* sh body or script name */
2366         if (newargs) g_string_append_printf(a, " %s", newargs); /* handler args */
2367
2368         for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
2369             if (spawnparts[i]) g_string_append_printf(a, " %s", spawnparts[i]);
2370
2371         g_array_append_val(rets, a->str);
2372         g_string_free(a, FALSE);
2373         g_strfreev(spawnparts);
2374     } else {
2375         gchar *origdup = g_strdup(origargs);
2376         g_array_append_val(rets, origdup);
2377     }
2378     return (gchar**)g_array_free(rets, FALSE);
2379 }
2380
2381 static void
2382 run_handler (const gchar *act, const gchar *args) {
2383     /* Consider this code a temporary hack to make the handlers usable.
2384        In practice, all this splicing, injection, and reconstruction is
2385        inefficient, annoying and hard to manage.  Potential pitfalls arise
2386        when the handler specific args 1) are not quoted  (the handler
2387        callbacks should take care of this)  2) are quoted but interfere
2388        with the users' own quotation.  A more ideal solution is
2389        to refactor parse_command so that it doesn't just take a string
2390        and execute it; rather than that, we should have a function which
2391        returns the argument vector parsed from the string.  This vector
2392        could be modified (e.g. insert additional args into it) before
2393        passing it to the next function that actually executes it.  Though
2394        it still isn't perfect for chain actions..  will reconsider & re-
2395        factor when I have the time. -duc */
2396
2397     char **parts = g_strsplit(act, " ", 2);
2398     if (!parts) return;
2399     if (g_strcmp0(parts[0], "chain") == 0) {
2400         GString *newargs = g_string_new("");
2401         gchar **chainparts = split_quoted(parts[1], FALSE);
2402
2403         /* for every argument in the chain, inject the handler args
2404            and make sure the new parts are wrapped in quotes */
2405         gchar **cp = chainparts;
2406         gchar quot = '\'';
2407         gchar *quotless = NULL;
2408         gchar **spliced_quotless = NULL; // sigh -_-;
2409         gchar **inpart = NULL;
2410
2411         while (*cp) {
2412             if ((**cp == '\'') || (**cp == '\"')) { /* strip old quotes */
2413                 quot = **cp;
2414                 quotless = g_strndup(&(*cp)[1], strlen(*cp) - 2);
2415             } else quotless = g_strdup(*cp);
2416
2417             spliced_quotless = g_strsplit(quotless, " ", 2);
2418             inpart = inject_handler_args(spliced_quotless[0], spliced_quotless[1], args);
2419             g_strfreev(spliced_quotless);
2420
2421             g_string_append_printf(newargs, " %c%s %s%c", quot, inpart[0], inpart[1], quot);
2422             g_free(quotless);
2423             g_strfreev(inpart);
2424             cp++;
2425         }
2426
2427         parse_command(parts[0], &(newargs->str[1]), NULL);
2428         g_string_free(newargs, TRUE);
2429         g_strfreev(chainparts);
2430
2431     } else {
2432         gchar **inparts = inject_handler_args(parts[0], parts[1], args);
2433         parse_command(inparts[0], inparts[1], NULL);
2434         g_free(inparts[0]);
2435         g_free(inparts[1]);
2436     }
2437     g_strfreev(parts);
2438 }
2439
2440 static void
2441 add_binding (const gchar *key, const gchar *act) {
2442     char **parts = g_strsplit(act, " ", 2);
2443     Action *action;
2444
2445     if (!parts)
2446         return;
2447
2448     //Debug:
2449     if (uzbl.state.verbose)
2450         printf ("Binding %-10s : %s\n", key, act);
2451     action = new_action(parts[0], parts[1]);
2452
2453     if (g_hash_table_remove (uzbl.bindings, key))
2454         g_warning ("Overwriting existing binding for \"%s\"", key);
2455     g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
2456     g_strfreev(parts);
2457 }
2458
2459 static gchar*
2460 get_xdg_var (XDG_Var xdg) {
2461     const gchar* actual_value = getenv (xdg.environmental);
2462     const gchar* home         = getenv ("HOME");
2463     gchar* return_value;
2464
2465     if (! actual_value || strcmp (actual_value, "") == 0) {
2466         if (xdg.default_value) {
2467             return_value = str_replace ("~", home, xdg.default_value);
2468         } else {
2469             return_value = NULL;
2470         }
2471     } else {
2472         return_value = str_replace("~", home, actual_value);
2473     }
2474
2475     return return_value;
2476 }
2477
2478 static gchar*
2479 find_xdg_file (int xdg_type, char* filename) {
2480     /* xdg_type = 0 => config
2481        xdg_type = 1 => data
2482        xdg_type = 2 => cache*/
2483
2484     gchar* xdgv = get_xdg_var (XDG[xdg_type]);
2485     gchar* temporary_file = g_strconcat (xdgv, filename, NULL);
2486     g_free (xdgv);
2487
2488     gchar* temporary_string;
2489     char*  saveptr;
2490     char*  buf;
2491
2492     if (! file_exists (temporary_file) && xdg_type != 2) {
2493         buf = get_xdg_var (XDG[3 + xdg_type]);
2494         temporary_string = (char *) strtok_r (buf, ":", &saveptr);
2495         g_free(buf);
2496
2497         while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) {
2498             g_free (temporary_file);
2499             temporary_file = g_strconcat (temporary_string, filename, NULL);
2500         }
2501     }
2502
2503     //g_free (temporary_string); - segfaults.
2504
2505     if (file_exists (temporary_file)) {
2506         return temporary_file;
2507     } else {
2508         return NULL;
2509     }
2510 }
2511 static void
2512 settings_init () {
2513     State *s = &uzbl.state;
2514     Network *n = &uzbl.net;
2515     int i;
2516     for (i = 0; default_config[i].command != NULL; i++) {
2517         parse_cmd_line(default_config[i].command, NULL);
2518     }
2519     
2520     if (g_strcmp0(s->config_file, "-") == 0) {
2521         s->config_file = NULL;
2522         create_stdin();
2523     }
2524
2525     else if (!s->config_file) {
2526         s->config_file = find_xdg_file (0, "/uzbl/config");
2527     }
2528
2529     if (s->config_file) {
2530         GArray* lines = read_file_by_line (s->config_file);
2531         int i = 0;
2532         gchar* line;
2533
2534         while ((line = g_array_index(lines, gchar*, i))) {
2535             parse_cmd_line (line, NULL);
2536             i ++;
2537             g_free (line);
2538         }
2539         g_array_free (lines, TRUE);
2540     } else {
2541         if (uzbl.state.verbose)
2542             printf ("No configuration file loaded.\n");
2543     }
2544
2545     g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL);
2546 }
2547
2548 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
2549     (void) session;
2550     (void) user_data;
2551     if (!uzbl.behave.cookie_handler)
2552          return;
2553
2554     soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
2555     GString *s = g_string_new ("");
2556     SoupURI * soup_uri = soup_message_get_uri(msg);
2557     g_string_printf(s, "GET '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path);
2558     run_handler(uzbl.behave.cookie_handler, s->str);
2559
2560     if(uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) {
2561         char *p = strchr(uzbl.comm.sync_stdout, '\n' );
2562         if ( p != NULL ) *p = '\0';
2563         soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout);
2564     }
2565     if (uzbl.comm.sync_stdout)
2566         uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout);
2567
2568     g_string_free(s, TRUE);
2569 }
2570
2571 static void
2572 save_cookies (SoupMessage *msg, gpointer user_data){
2573     (void) user_data;
2574     GSList *ck;
2575     char *cookie;
2576     for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
2577         cookie = soup_cookie_to_set_cookie_header(ck->data);
2578         SoupURI * soup_uri = soup_message_get_uri(msg);
2579         GString *s = g_string_new ("");
2580         g_string_printf(s, "PUT '%s' '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path, cookie);
2581         run_handler(uzbl.behave.cookie_handler, s->str);
2582         g_free (cookie);
2583         g_string_free(s, TRUE);
2584     }
2585     g_slist_free(ck);
2586 }
2587
2588 /* --- WEBINSPECTOR --- */
2589 static void
2590 hide_window_cb(GtkWidget *widget, gpointer data) {
2591     (void) data;
2592
2593     gtk_widget_hide(widget);
2594 }
2595
2596 static WebKitWebView*
2597 create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
2598     (void) data;
2599     (void) page;
2600     (void) web_inspector;
2601     GtkWidget* scrolled_window;
2602     GtkWidget* new_web_view;
2603     GUI *g = &uzbl.gui;
2604
2605     g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2606     g_signal_connect(G_OBJECT(g->inspector_window), "delete-event",
2607             G_CALLBACK(hide_window_cb), NULL);
2608
2609     gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector");
2610     gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300);
2611     gtk_widget_show(g->inspector_window);
2612
2613     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2614     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2615             GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2616     gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window);
2617     gtk_widget_show(scrolled_window);
2618
2619     new_web_view = webkit_web_view_new();
2620     gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view);
2621
2622     return WEBKIT_WEB_VIEW(new_web_view);
2623 }
2624
2625 static gboolean
2626 inspector_show_window_cb (WebKitWebInspector* inspector){
2627     (void) inspector;
2628     gtk_widget_show(uzbl.gui.inspector_window);
2629     return TRUE;
2630 }
2631
2632 /* TODO: Add variables and code to make use of these functions */
2633 static gboolean
2634 inspector_close_window_cb (WebKitWebInspector* inspector){
2635     (void) inspector;
2636     return TRUE;
2637 }
2638
2639 static gboolean
2640 inspector_attach_window_cb (WebKitWebInspector* inspector){
2641     (void) inspector;
2642     return FALSE;
2643 }
2644
2645 static gboolean
2646 inspector_detach_window_cb (WebKitWebInspector* inspector){
2647     (void) inspector;
2648     return FALSE;
2649 }
2650
2651 static gboolean
2652 inspector_uri_changed_cb (WebKitWebInspector* inspector){
2653     (void) inspector;
2654     return FALSE;
2655 }
2656
2657 static gboolean
2658 inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
2659     (void) inspector;
2660     return FALSE;
2661 }
2662
2663 static void
2664 set_up_inspector() {
2665     GUI *g = &uzbl.gui;
2666     WebKitWebSettings *settings = view_settings();
2667     g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);
2668
2669     uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view);
2670     g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL);
2671     g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL);
2672     g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL);
2673     g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL);
2674     g_signal_connect (G_OBJECT (g->inspector), "detach-window", G_CALLBACK (inspector_detach_window_cb), NULL);
2675     g_signal_connect (G_OBJECT (g->inspector), "finished", G_CALLBACK (inspector_inspector_destroyed_cb), NULL);
2676
2677     g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL);
2678 }
2679
2680 static void
2681 dump_var_hash(gpointer k, gpointer v, gpointer ud) {
2682     (void) ud;
2683     uzbl_cmdprop *c = v;
2684
2685     if(!c->dump)
2686         return;
2687
2688     if(c->type == TYPE_STR)
2689         printf("set %s = %s\n", (char *)k, *c->ptr?(char *)*c->ptr:" ");
2690     else if(c->type == TYPE_INT)
2691         printf("set %s = %d\n", (char *)k, (int)*c->ptr);
2692 }
2693
2694 static void
2695 dump_key_hash(gpointer k, gpointer v, gpointer ud) {
2696     (void) ud;
2697     Action *a = v;
2698
2699     printf("bind %s = %s %s\n", (char *)k ,
2700             (char *)a->name, a->param?(char *)a->param:"");
2701 }
2702
2703 static void
2704 dump_config() {
2705     g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL);
2706     g_hash_table_foreach(uzbl.bindings, dump_key_hash, NULL);
2707 }
2708
2709 /** -- MAIN -- **/
2710 int
2711 main (int argc, char* argv[]) {
2712     gtk_init (&argc, &argv);
2713     if (!g_thread_supported ())
2714         g_thread_init (NULL);
2715     uzbl.state.executable_path = g_strdup(argv[0]);
2716     uzbl.state.selected_url = NULL;
2717     uzbl.state.searchtx = NULL;
2718
2719     GOptionContext* context = g_option_context_new ("[ uri ] - load a uri by default");
2720     g_option_context_add_main_entries (context, entries, NULL);
2721     g_option_context_add_group (context, gtk_get_option_group (TRUE));
2722     g_option_context_parse (context, &argc, &argv, NULL);
2723     g_option_context_free(context);
2724
2725     if (uzbl.behave.print_version) {
2726         printf("Commit: %s\n", COMMIT);
2727         exit(0);
2728     }
2729
2730     gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL);
2731     if (argc > 1 && !uzbl.state.uri)
2732         uri_override = g_strdup(argv[1]);
2733     gboolean verbose_override = uzbl.state.verbose;
2734
2735     /* initialize hash table */
2736     uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
2737
2738     uzbl.net.soup_session = webkit_get_default_session();
2739     uzbl.state.keycmd = g_string_new("");
2740
2741     if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
2742         fprintf(stderr, "uzbl: error hooking SIGTERM\n");
2743     if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
2744         fprintf(stderr, "uzbl: error hooking SIGINT\n");
2745     if(setup_signal(SIGALRM, catch_alrm) == SIG_ERR)
2746         fprintf(stderr, "uzbl: error hooking SIGALARM\n");
2747
2748
2749     if(uname(&uzbl.state.unameinfo) == -1)
2750         g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");
2751
2752     uzbl.gui.sbar.progress_s = g_strdup("=");
2753     uzbl.gui.sbar.progress_u = g_strdup("·");
2754     uzbl.gui.sbar.progress_w = 10;
2755
2756     /* HTML mode defaults*/
2757     uzbl.behave.html_buffer = g_string_new("");
2758     uzbl.behave.html_endmarker = g_strdup(".");
2759     uzbl.behave.html_timeout = 60;
2760     uzbl.behave.base_url = g_strdup("http://invalid");
2761
2762     /* default mode indicators */
2763     uzbl.behave.insert_indicator = g_strdup("I");
2764     uzbl.behave.cmd_indicator    = g_strdup("C");
2765
2766     setup_scanner();
2767     commands_hash ();
2768     make_var_to_name_hash();
2769
2770     uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
2771
2772     uzbl.gui.scrolled_win = create_browser();
2773     create_mainbar();
2774
2775     /* initial packing */
2776     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
2777     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
2778
2779     if (uzbl.state.socket_id) {
2780         uzbl.gui.plug = create_plug ();
2781         gtk_container_add (GTK_CONTAINER (uzbl.gui.plug), uzbl.gui.vbox);
2782         gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
2783     } else {
2784         uzbl.gui.main_window = create_window ();
2785         gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
2786         gtk_widget_show_all (uzbl.gui.main_window);
2787         uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
2788     }
2789
2790     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
2791
2792     if (uzbl.state.verbose) {
2793         printf("Uzbl start location: %s\n", argv[0]);
2794         if (uzbl.state.socket_id)
2795             printf("plug_id %i\n", gtk_plug_get_id(uzbl.gui.plug));
2796         else
2797             printf("window_id %i\n",(int) uzbl.xwin);
2798         printf("pid %i\n", getpid ());
2799         printf("name: %s\n", uzbl.state.instance_name);
2800     }
2801
2802     uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
2803     uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
2804     uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
2805     uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
2806     gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
2807
2808     settings_init ();
2809
2810     if (!uzbl.behave.show_status)
2811         gtk_widget_hide(uzbl.gui.mainbar);
2812     else
2813         update_title();
2814
2815     /* WebInspector */
2816     set_up_inspector();
2817
2818     if (verbose_override > uzbl.state.verbose)
2819         uzbl.state.verbose = verbose_override;
2820
2821     if (uri_override) {
2822         set_var_value("uri", uri_override);
2823         g_free(uri_override);
2824     } else if (uzbl.state.uri)
2825         cmd_load_uri(uzbl.gui.web_view, NULL);
2826
2827     gtk_main ();
2828     clean_up();
2829
2830     return EXIT_SUCCESS;
2831 }
2832
2833 /* vi: set et ts=4: */