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