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