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