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