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