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