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