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