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