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