/* -*- c-basic-offset: 4; -*- */
// Original code taken from the example webkit-gtk+ application. see notice below.
// Modified code is licensed under the GPL 3.  See LICENSE file.


/*
 * Copyright (C) 2006, 2007 Apple Inc.
 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#define LENGTH(x) (sizeof x / sizeof x[0])
#define MAX_BINDINGS 256
#define _POSIX_SOURCE

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <webkit/webkit.h>
#include <libsoup/soup.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "uzbl.h"
#include "config.h"

static Uzbl uzbl;
typedef void (*Command)(WebKitWebView*, GArray *argv);



/* commandline arguments (set initial values for the state variables) */
static const 
GOptionEntry entries[] =
{
    { "uri",     'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri,
        "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
    { "verbose", 'v', 0, G_OPTION_ARG_NONE,   &uzbl.state.verbose,
        "Whether to print all messages or just errors.", NULL },
    { "name",    'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, 
        "Name of the current instance (defaults to Xorg window id)", "NAME" },
    { "config",  'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file,   
        "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
    { NULL,      0, 0, 0, NULL, NULL, NULL }
};

/* associate command names to their properties */
typedef const struct {
    void **ptr;
    int type;
    int dump;
    void (*func)(void);
} uzbl_cmdprop;

enum {TYPE_INT, TYPE_STR, TYPE_FLOAT};

/* an abbreviation to help keep the table's width humane */
#define PTR(var, t, d, fun) { .ptr = (void*)&(var), .type = TYPE_##t, .dump = d, .func = fun }

const struct {
    char *name;
    uzbl_cmdprop cp;
} var_name_to_ptr[] = {
/*    variable name         pointer to variable in code          type  dump callback function    */
/*  --------------------------------------------------------------------------------------- */
    { "uri",                 PTR(uzbl.state.uri,                  STR,  1,   cmd_load_uri)},
    { "verbose",             PTR(uzbl.state.verbose,              INT,  1,   NULL)},
    { "mode",                PTR(uzbl.behave.mode,                INT,  0,   NULL)},
    { "inject_html",         PTR(uzbl.behave.inject_html,         STR,  0,   cmd_inject_html)},
    { "base_url",            PTR(uzbl.behave.base_url,            STR,  1,   NULL)},
    { "html_endmarker",      PTR(uzbl.behave.html_endmarker,      STR,  1,   NULL)},
    { "html_mode_timeout",   PTR(uzbl.behave.html_timeout,        INT,  1,   NULL)},
    { "status_message",      PTR(uzbl.gui.sbar.msg,               STR,  1,   update_title)},
    { "show_status",         PTR(uzbl.behave.show_status,         INT,  1,   cmd_set_status)},
    { "status_top",          PTR(uzbl.behave.status_top,          INT,  1,   move_statusbar)},
    { "status_format",       PTR(uzbl.behave.status_format,       STR,  1,   update_title)},
    { "status_pbar_done",    PTR(uzbl.gui.sbar.progress_s,        STR,  1,   update_title)},
    { "status_pbar_pending", PTR(uzbl.gui.sbar.progress_u,        STR,  1,   update_title)},
    { "status_pbar_width",   PTR(uzbl.gui.sbar.progress_w,        INT,  1,   update_title)},
    { "status_background",   PTR(uzbl.behave.status_background,   STR,  1,   update_title)},
    { "insert_indicator",    PTR(uzbl.behave.insert_indicator,    STR,  1,   update_title)},
    { "command_indicator",   PTR(uzbl.behave.cmd_indicator,       STR,  1,   update_title)},
    { "title_format_long",   PTR(uzbl.behave.title_format_long,   STR,  1,   update_title)},
    { "title_format_short",  PTR(uzbl.behave.title_format_short,  STR,  1,   update_title)},
    { "insert_mode",         PTR(uzbl.behave.insert_mode,         INT,  1,   NULL)},
    { "always_insert_mode",  PTR(uzbl.behave.always_insert_mode,  INT,  1,   cmd_always_insert_mode)},
    { "reset_command_mode",  PTR(uzbl.behave.reset_command_mode,  INT,  1,   NULL)},
    { "modkey",              PTR(uzbl.behave.modkey,              STR,  1,   cmd_modkey)},
    { "load_finish_handler", PTR(uzbl.behave.load_finish_handler, STR,  1,   NULL)},
    { "load_start_handler",  PTR(uzbl.behave.load_start_handler,  STR,  1,   NULL)},
    { "load_commit_handler", PTR(uzbl.behave.load_commit_handler, STR,  1,   NULL)},
    { "history_handler",     PTR(uzbl.behave.history_handler,     STR,  1,   NULL)},
    { "download_handler",    PTR(uzbl.behave.download_handler,    STR,  1,   NULL)},
    { "cookie_handler",      PTR(uzbl.behave.cookie_handler,      STR,  1,   cmd_cookie_handler)},
    { "fifo_dir",            PTR(uzbl.behave.fifo_dir,            STR,  1,   cmd_fifo_dir)},
    { "socket_dir",          PTR(uzbl.behave.socket_dir,          STR,  1,   cmd_socket_dir)},
    { "http_debug",          PTR(uzbl.behave.http_debug,          INT,  1,   cmd_http_debug)},
    { "shell_cmd",           PTR(uzbl.behave.shell_cmd,           STR,  1,   NULL)},
    { "proxy_url",           PTR(uzbl.net.proxy_url,              STR,  1,   set_proxy_url)},
    { "max_conns",           PTR(uzbl.net.max_conns,              INT,  1,   cmd_max_conns)},
    { "max_conns_host",      PTR(uzbl.net.max_conns_host,         INT,  1,   cmd_max_conns_host)},
    { "useragent",           PTR(uzbl.net.useragent,              STR,  1,   cmd_useragent)},
    /* exported WebKitWebSettings properties */
    { "zoom_level",          PTR(uzbl.behave.zoom_level,          FLOAT,1,   cmd_zoom_level)},
    { "font_size",           PTR(uzbl.behave.font_size,           INT,  1,   cmd_font_size)},
    { "monospace_size",      PTR(uzbl.behave.monospace_size,      INT,  1,   cmd_font_size)},
    { "minimum_font_size",   PTR(uzbl.behave.minimum_font_size,   INT,  1,   cmd_minimum_font_size)},
    { "disable_plugins",     PTR(uzbl.behave.disable_plugins,     INT,  1,   cmd_disable_plugins)},
    { "disable_scripts",     PTR(uzbl.behave.disable_scripts,     INT,  1,   cmd_disable_scripts)},
    { "autoload_images",     PTR(uzbl.behave.autoload_img,        INT,  1,   cmd_autoload_img)},
    { "autoshrink_images",   PTR(uzbl.behave.autoshrink_img,      INT,  1,   cmd_autoshrink_img)},
    { "enable_spellcheck",   PTR(uzbl.behave.enable_spellcheck,   INT,  1,   cmd_enable_spellcheck)},
    { "enable_private",      PTR(uzbl.behave.enable_private,      INT,  1,   cmd_enable_private)},
    { "print_backgrounds",   PTR(uzbl.behave.print_bg,            INT,  1,   cmd_print_bg)},
    { "stylesheet_uri",      PTR(uzbl.behave.style_uri,           STR,  1,   cmd_style_uri)},
    { "resizable_text_areas",PTR(uzbl.behave.resizable_txt,       INT,  1,   cmd_resizable_txt)},
    { "default_encoding",    PTR(uzbl.behave.default_encoding,    STR,  1,   cmd_default_encoding)},
    { "enforce_96_dpi",      PTR(uzbl.behave.enforce_96dpi,       INT,  1,   cmd_enforce_96dpi)},
    { "caret_browsing",      PTR(uzbl.behave.caret_browsing,      INT,  1,   cmd_caret_browsing)},

    { NULL,                  {.ptr = NULL, .type = TYPE_INT, .dump = 0, .func = NULL}}
}, *n2v_p = var_name_to_ptr;

const struct {
    char *key;
    guint mask;
} modkeys[] = {
    { "SHIFT",   GDK_SHIFT_MASK   }, // shift
    { "LOCK",    GDK_LOCK_MASK    }, // capslock or shiftlock, depending on xserver's modmappings
    { "CONTROL", GDK_CONTROL_MASK }, // control
    { "MOD1",    GDK_MOD1_MASK    }, // 4th mod - normally alt but depends on modmappings
    { "MOD2",    GDK_MOD2_MASK    }, // 5th mod
    { "MOD3",    GDK_MOD3_MASK    }, // 6th mod
    { "MOD4",    GDK_MOD4_MASK    }, // 7th mod
    { "MOD5",    GDK_MOD5_MASK    }, // 8th mod
    { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
    { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
    { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
    { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
    { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
    { "SUPER",   GDK_SUPER_MASK   }, // super (since 2.10)
    { "HYPER",   GDK_HYPER_MASK   }, // hyper (since 2.10)
    { "META",    GDK_META_MASK    }, // meta (since 2.10)
    { NULL,      0                }
};


/* construct a hash from the var_name_to_ptr array for quick access */
static void
make_var_to_name_hash() {
    uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
    while(n2v_p->name) {
        g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
        n2v_p++;
    }
}

/* --- UTILITY FUNCTIONS --- */
static gchar *
expand_vars(char *s) {
    uzbl_cmdprop *c;
    char upto = ' ';
    char ret[256],  *vend;
    GString *buf = g_string_new("");

    while(*s) {
        switch(*s) {
            case '\\':
                g_string_append_c(buf, *++s);
                s++;
                break;
            case '@':
                if(*(s+1) == '{') {
                    upto = '}'; s++;
                }
                s++;
                if( (vend = strchr(s, upto)) ||
                        (vend = strchr(s, '\0')) ) {
                    strncpy(ret, s, vend-s);
                    ret[vend-s] = '\0';
                    if( (c = g_hash_table_lookup(uzbl.comm.proto_var, ret)) ) {
                        if(c->type == TYPE_STR)
                            g_string_append(buf, (gchar *)*c->ptr);
                        else if(c->type == TYPE_INT) {
                            char *b = itos((int)*c->ptr);
                            g_string_append(buf, b);
                            g_free(b);
                        }
                    }
                    if(upto == ' ') s = vend;
                    else s = vend+1;
                    upto = ' ';
                }
                break;
            default:
                g_string_append_c(buf, *s);
                s++;
                break;
        }
    }
    return g_string_free(buf, FALSE);
}

char *
itos(int val) {
    char tmp[20];

    snprintf(tmp, sizeof(tmp), "%i", val);
    return g_strdup(tmp);
}

static gchar*
strfree(gchar *str) { g_free(str); return NULL; }  // for freeing & setting to null in one go

static gchar*
argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }

static char *
str_replace (const char* search, const char* replace, const char* string) {
    gchar **buf;
    char *ret;

    buf = g_strsplit (string, search, -1);
    ret = g_strjoinv (replace, buf);
    g_strfreev(buf); // somebody said this segfaults

    return ret;
}

static GArray*
read_file_by_line (gchar *path) {
    GIOChannel *chan = NULL;
    gchar *readbuf = NULL;
    gsize len;
    GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
    int i = 0;
    
    chan = g_io_channel_new_file(path, "r", NULL);
    
    if (chan) {
        while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
            const gchar* val = g_strdup (readbuf);
            g_array_append_val (lines, val);
            g_free (readbuf);
            i ++;
        }
        
        g_io_channel_unref (chan);
    } else {
        fprintf(stderr, "File '%s' not be read.\n", path);
    }
    
    return lines;
}

static
gchar* parseenv (char* string) {
    extern char** environ;
    gchar* tmpstr = NULL;
    int i = 0;
    

    while (environ[i] != NULL) {
        gchar** env = g_strsplit (environ[i], "=", 2);
        gchar* envname = g_strconcat ("$", env[0], NULL);

        if (g_strrstr (string, envname) != NULL) {
            tmpstr = g_strdup(string);
            g_free (string);
            string = str_replace(envname, env[1], tmpstr);
            g_free (tmpstr);
        }

        g_free (envname);
        g_strfreev (env); // somebody said this breaks uzbl
        i++;
    }

    return string;
}

static sigfunc*
setup_signal(int signr, sigfunc *shandler) {
    struct sigaction nh, oh;

    nh.sa_handler = shandler;
    sigemptyset(&nh.sa_mask);
    nh.sa_flags = 0;

    if(sigaction(signr, &nh, &oh) < 0)
        return SIG_ERR;

    return NULL;
}

static void
clean_up(void) {
    if (uzbl.behave.fifo_dir)
        unlink (uzbl.comm.fifo_path);
    if (uzbl.behave.socket_dir)
        unlink (uzbl.comm.socket_path);

    g_free(uzbl.state.executable_path);
    g_string_free(uzbl.state.keycmd, TRUE);
    g_hash_table_destroy(uzbl.bindings);
    g_hash_table_destroy(uzbl.behave.commands);
}

/* used for html_mode_timeout 
 * be sure to extend this function to use
 * more timers if needed in other places
*/
static void
set_timeout(int seconds) {
    struct itimerval t;
    memset(&t, 0, sizeof t);

    t.it_value.tv_sec =  seconds;
    t.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &t, NULL);
}

/* --- SIGNAL HANDLER --- */

static void
catch_sigterm(int s) {
    (void) s;
    clean_up();
}

static void
catch_sigint(int s) {
    (void) s;
    clean_up();
    exit(EXIT_SUCCESS);
}

static void
catch_alrm(int s) {
    (void) s;

    set_var_value("mode", "0");
    render_html();
}


/* --- CALLBACKS --- */

static gboolean
new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
    (void) web_view;
    (void) frame;
    (void) navigation_action;
    (void) policy_decision;
    (void) user_data;
    const gchar* uri = webkit_network_request_get_uri (request);
    if (uzbl.state.verbose)
        printf("New window requested -> %s \n", uri);
    new_window_load_uri(uri);
    return (FALSE);
}

static gboolean
mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
    (void) frame;
    (void) request;
    (void) user_data;

    /* If we can display it, let's display it... */
    if (webkit_web_view_can_show_mime_type (web_view, mime_type)) {
        webkit_web_policy_decision_use (policy_decision);
        return TRUE;
    }

    /* ...everything we can't displayed is downloaded */
    webkit_web_policy_decision_download (policy_decision);
    return TRUE;
}

WebKitWebView*
create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer user_data) {
    (void) web_view;
    (void) frame;
    (void) user_data;
    if (uzbl.state.selected_url != NULL) {
        if (uzbl.state.verbose)
            printf("\nNew web view -> %s\n",uzbl.state.selected_url);
        new_window_load_uri(uzbl.state.selected_url);
    } else {
        if (uzbl.state.verbose)
            printf("New web view -> %s\n","Nothing to open, exiting");
    }
    return (NULL);
}

static gboolean
download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
    (void) web_view;
    (void) user_data;
    if (uzbl.behave.download_handler) {
        const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
        if (uzbl.state.verbose)
            printf("Download -> %s\n",uri);
        /* if urls not escaped, we may have to escape and quote uri before this call */
        run_handler(uzbl.behave.download_handler, uri);
    }
    return (FALSE);
}

/* scroll a bar in a given direction */
static void
scroll (GtkAdjustment* bar, GArray *argv) {
    gdouble amount;
    gchar *end;

    amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
    if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
    gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
}

static void
scroll_begin(WebKitWebView* page, GArray *argv) {
    (void) page; (void) argv;
    gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
}

static void
scroll_end(WebKitWebView* page, GArray *argv) {
    (void) page; (void) argv;
    gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
                              gtk_adjustment_get_page_size(uzbl.gui.bar_v));
}

static void
scroll_vert(WebKitWebView* page, GArray *argv) {
    (void) page;
    scroll(uzbl.gui.bar_v, argv);
}

static void
scroll_horz(WebKitWebView* page, GArray *argv) {
    (void) page;
    scroll(uzbl.gui.bar_h, argv);
}

static void
cmd_set_status() {
    if (!uzbl.behave.show_status) {
        gtk_widget_hide(uzbl.gui.mainbar);
    } else {
        gtk_widget_show(uzbl.gui.mainbar);
    }
    update_title();
}

static void
toggle_status_cb (WebKitWebView* page, GArray *argv) {
    (void)page;
    (void)argv;

    if (uzbl.behave.show_status) {
        gtk_widget_hide(uzbl.gui.mainbar);
    } else {
        gtk_widget_show(uzbl.gui.mainbar);
    }
    uzbl.behave.show_status = !uzbl.behave.show_status;
    update_title();
}

static void
link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
    (void) page;
    (void) title;
    (void) data;
    //Set selected_url state variable
    g_free(uzbl.state.selected_url);
    uzbl.state.selected_url = NULL;
    if (link) {
        uzbl.state.selected_url = g_strdup(link);
    }
    update_title();
}

static void
title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
    (void) web_view;
    (void) web_frame;
    (void) data;
    if (uzbl.gui.main_title)
        g_free (uzbl.gui.main_title);
    uzbl.gui.main_title = g_strdup (title);
    update_title();
}

static void
progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
    (void) page;
    (void) data;
    uzbl.gui.sbar.load_progress = progress;
    update_title();
}

static void
load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
    (void) page;
    (void) frame;
    (void) data;
    if (uzbl.behave.load_finish_handler)
        run_handler(uzbl.behave.load_finish_handler, "");
}

static void
load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
    (void) page;
    (void) frame;
    (void) data;
    uzbl.gui.sbar.load_progress = 0;
    g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
    if (uzbl.behave.load_start_handler)
        run_handler(uzbl.behave.load_start_handler, "");
}

static void
load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
    (void) page;
    (void) data;
    g_free (uzbl.state.uri);
    GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
    uzbl.state.uri = g_string_free (newuri, FALSE);
    if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
        uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
        update_title();
    }
    if (uzbl.behave.load_commit_handler)
        run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
}

static void
destroy_cb (GtkWidget* widget, gpointer data) {
    (void) widget;
    (void) data;
    gtk_main_quit ();
}

static void
log_history_cb () {
   if (uzbl.behave.history_handler) {
       time_t rawtime;
       struct tm * timeinfo;
       char date [80];
       time ( &rawtime );
       timeinfo = localtime ( &rawtime );
       strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
       run_handler(uzbl.behave.history_handler, date);
   }
}


/* VIEW funcs (little webkit wrappers) */
#define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
VIEWFUNC(reload)
VIEWFUNC(reload_bypass_cache)
VIEWFUNC(stop_loading)
VIEWFUNC(zoom_in)
VIEWFUNC(zoom_out)
VIEWFUNC(go_back)
VIEWFUNC(go_forward)
#undef VIEWFUNC

/* -- command to callback/function map for things we cannot attach to any signals */
static struct {char *name; Command command[2];} cmdlist[] =
{   /* key                   function      no_split      */
    { "back",               {view_go_back, 0}              },
    { "forward",            {view_go_forward, 0}           },
    { "scroll_vert",        {scroll_vert, 0}               },
    { "scroll_horz",        {scroll_horz, 0}               },
    { "scroll_begin",       {scroll_begin, 0}              },
    { "scroll_end",         {scroll_end, 0}                },
    { "reload",             {view_reload, 0},              },
    { "reload_ign_cache",   {view_reload_bypass_cache, 0}  },
    { "stop",               {view_stop_loading, 0},        },
    { "zoom_in",            {view_zoom_in, 0},             }, //Can crash (when max zoom reached?).
    { "zoom_out",           {view_zoom_out, 0},            },
    { "uri",                {load_uri, NOSPLIT}            },
    { "js",                 {run_js, NOSPLIT}              },
    { "script",             {run_external_js, 0}           },
    { "toggle_status",      {toggle_status_cb, 0}          },
    { "spawn",              {spawn, 0}                     },
    { "sync_spawn",         {spawn_sync, 0}                }, // needed for cookie handler
    { "sh",                 {spawn_sh, 0}                  },
    { "sync_sh",            {spawn_sh_sync, 0}             }, // needed for cookie handler
    { "exit",               {close_uzbl, 0}                },
    { "search",             {search_forward_text, NOSPLIT} },
    { "search_reverse",     {search_reverse_text, NOSPLIT} },
    { "dehilight",          {dehilight, 0}                 },
    { "toggle_insert_mode", {toggle_insert_mode, 0}        },
    { "set",                {set_var, NOSPLIT}             },
    //{ "get",                {get_var, NOSPLIT}             },
    { "bind",               {act_bind, NOSPLIT}            },
    { "dump_config",        {act_dump_config, 0}           },
    { "keycmd",             {keycmd, NOSPLIT}              },
    { "keycmd_nl",          {keycmd_nl, NOSPLIT}           },
    { "keycmd_bs",          {keycmd_bs, 0}                 },
    { "chain",              {chain, 0}                     },
    { "print",              {print, NOSPLIT}               }
};

static void
commands_hash(void)
{
    unsigned int i;
    uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);

    for (i = 0; i < LENGTH(cmdlist); i++)
        g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
}

/* -- CORE FUNCTIONS -- */

void
free_action(gpointer act) {
    Action *action = (Action*)act;
    g_free(action->name);
    if (action->param)
        g_free(action->param);
    g_free(action);
}

Action*
new_action(const gchar *name, const gchar *param) {
    Action *action = g_new(Action, 1);

    action->name = g_strdup(name);
    if (param)
        action->param = g_strdup(param);
    else
        action->param = NULL;

    return action;
}

static bool
file_exists (const char * filename) {
    return (access(filename, F_OK) == 0);
}

static void
set_var(WebKitWebView *page, GArray *argv) {
    (void) page;
    gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2);
    gchar *value = parseenv(g_strdup(split[1] ? g_strchug(split[1]) : " "));
    set_var_value(g_strstrip(split[0]), value);
    g_free(value);
    g_strfreev(split);
}

static void
print(WebKitWebView *page, GArray *argv) {
    (void) page;
    gchar* buf;

    buf = expand_vars(argv_idx(argv, 0));
    puts(buf);
    g_free(buf);
}

static void
act_bind(WebKitWebView *page, GArray *argv) {
    (void) page;
    gchar **split = g_strsplit(argv_idx(argv, 0), " = ", 2);
    gchar *value = parseenv(g_strdup(split[1] ? g_strchug(split[1]) : " "));
    add_binding(g_strstrip(split[0]), value);
    g_free(value);
    g_strfreev(split);
}


static void
act_dump_config() {
    dump_config();
}

static void
toggle_insert_mode(WebKitWebView *page, GArray *argv) {
    (void)page;

    if (argv_idx(argv, 0)) {
        if (strcmp (argv_idx(argv, 0), "0") == 0) {
            uzbl.behave.insert_mode = FALSE;
        } else {
            uzbl.behave.insert_mode = TRUE;
        }
    } else {
        uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
    }

    update_title();
}

static void
load_uri (WebKitWebView *web_view, GArray *argv) {
    if (argv_idx(argv, 0)) {
        GString* newuri = g_string_new (argv_idx(argv, 0));
        if (g_strstr_len (argv_idx(argv, 0), 11, "javascript:") != NULL) {
            run_js(web_view, argv);
            return;
        }
        if (g_strrstr (argv_idx(argv, 0), "://") == NULL && g_strstr_len (argv_idx(argv, 0), 5, "data:") == NULL)
            g_string_prepend (newuri, "http://");
        /* if we do handle cookies, ask our handler for them */
        webkit_web_view_load_uri (web_view, newuri->str);
        g_string_free (newuri, TRUE);
    }
}

static void
run_js (WebKitWebView * web_view, GArray *argv) {
    if (argv_idx(argv, 0))
        webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
}

static void
run_external_js (WebKitWebView * web_view, GArray *argv) {
    if (argv_idx(argv, 0)) {
        GArray* lines = read_file_by_line (argv_idx (argv, 0));
        gchar*  js = NULL;
        int i = 0;
        gchar* line;

        while ((line = g_array_index(lines, gchar*, i))) {
            if (js == NULL) {
                js = g_strdup (line);
            } else {
                gchar* newjs = g_strconcat (js, line, NULL);
                js = newjs;
            }
            i ++;
            g_free (line);
        }
        
        if (uzbl.state.verbose)
            printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0));

        if (argv_idx (argv, 1)) {
            gchar* newjs = str_replace("%s", argv_idx (argv, 1), js);
            g_free (js);
            js = newjs;
        }
        webkit_web_view_execute_script (web_view, js);
        g_free (js);
        g_array_free (lines, TRUE);
    }
}

static void
search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
    if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) {
        if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) {
            webkit_web_view_unmark_text_matches (page);
            webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0);
            uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
        }
    }
    
    if (uzbl.state.searchtx) {
        if (uzbl.state.verbose)
            printf ("Searching: %s\n", uzbl.state.searchtx);
        webkit_web_view_set_highlight_text_matches (page, TRUE);
        webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
    }
}

static void
search_forward_text (WebKitWebView *page, GArray *argv) {
    search_text(page, argv, TRUE);
}

static void
search_reverse_text (WebKitWebView *page, GArray *argv) {
    search_text(page, argv, FALSE);
}

static void
dehilight (WebKitWebView *page, GArray *argv) {
    (void) argv;
    webkit_web_view_set_highlight_text_matches (page, FALSE);
}


static void
new_window_load_uri (const gchar * uri) {
    GString* to_execute = g_string_new ("");
    g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
    int i;
    for (i = 0; entries[i].long_name != NULL; i++) {
        if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
            gchar** str = (gchar**)entries[i].arg_data;
            if (*str!=NULL) {
                g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
            }
        }
    }
    if (uzbl.state.verbose)
        printf("\n%s\n", to_execute->str);
    g_spawn_command_line_async (to_execute->str, NULL);
    g_string_free (to_execute, TRUE);
}

static void
chain (WebKitWebView *page, GArray *argv) {
    (void)page;
    gchar *a = NULL;
    gchar **parts = NULL;
    guint i = 0;    
    while ((a = argv_idx(argv, i++))) {
        parts = g_strsplit (a, " ", 2);
        parse_command(parts[0], parts[1]);
        g_strfreev (parts);
    }
}

static void
keycmd (WebKitWebView *page, GArray *argv) {
    (void)page;
    (void)argv;
    g_string_assign(uzbl.state.keycmd, argv_idx(argv, 0));
    run_keycmd(FALSE);
    update_title();
}

static void
keycmd_nl (WebKitWebView *page, GArray *argv) {
    (void)page;
    (void)argv;
    g_string_assign(uzbl.state.keycmd, argv_idx(argv, 0));
    run_keycmd(TRUE);
    update_title();
}

static void
keycmd_bs (WebKitWebView *page, GArray *argv) {
    (void)page;
    (void)argv;
    g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
    update_title();
}

static void
close_uzbl (WebKitWebView *page, GArray *argv) {
    (void)page;
    (void)argv;
    gtk_main_quit ();
}

/* --Statusbar functions-- */
static char*
build_progressbar_ascii(int percent) {
   int width=uzbl.gui.sbar.progress_w;
   int i;
   double l;
   GString *bar = g_string_new("");

   l = (double)percent*((double)width/100.);
   l = (int)(l+.5)>=(int)l ? l+.5 : l;

   for(i=0; i<(int)l; i++)
       g_string_append(bar, uzbl.gui.sbar.progress_s);

   for(; i<width; i++)
       g_string_append(bar, uzbl.gui.sbar.progress_u);

   return g_string_free(bar, FALSE);
}

static void
setup_scanner() {
     const GScannerConfig scan_config = {
             (
              "\t\r\n"
             )            /* cset_skip_characters */,
             (
              G_CSET_a_2_z
              "_#"
              G_CSET_A_2_Z
             )            /* cset_identifier_first */,
             (
              G_CSET_a_2_z
              "_0123456789"
              G_CSET_A_2_Z
              G_CSET_LATINS
              G_CSET_LATINC
             )            /* cset_identifier_nth */,
             ( "" )    /* cpair_comment_single */,

             TRUE         /* case_sensitive */,

             FALSE        /* skip_comment_multi */,
             FALSE        /* skip_comment_single */,
             FALSE        /* scan_comment_multi */,
             TRUE         /* scan_identifier */,
             TRUE         /* scan_identifier_1char */,
             FALSE        /* scan_identifier_NULL */,
             TRUE         /* scan_symbols */,
             FALSE        /* scan_binary */,
             FALSE        /* scan_octal */,
             FALSE        /* scan_float */,
             FALSE        /* scan_hex */,
             FALSE        /* scan_hex_dollar */,
             FALSE        /* scan_string_sq */,
             FALSE        /* scan_string_dq */,
             TRUE         /* numbers_2_int */,
             FALSE        /* int_2_float */,
             FALSE        /* identifier_2_string */,
             FALSE        /* char_2_token */,
             FALSE        /* symbol_2_token */,
             TRUE         /* scope_0_fallback */,
             FALSE,
             TRUE
     };

     uzbl.scan = g_scanner_new(&scan_config);
     while(symp->symbol_name) {
         g_scanner_scope_add_symbol(uzbl.scan, 0,
                         symp->symbol_name,
                         GINT_TO_POINTER(symp->symbol_token));
         symp++;
     }
}

static gchar *
expand_template(const char *template, gboolean escape_markup) {
     if(!template) return NULL;

     GTokenType token = G_TOKEN_NONE;
     GString *ret = g_string_new("");
     char *buf=NULL;
     int sym;

     g_scanner_input_text(uzbl.scan, template, strlen(template));
     while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
         token = g_scanner_get_next_token(uzbl.scan);

         if(token == G_TOKEN_SYMBOL) {
             sym = GPOINTER_TO_INT(g_scanner_cur_value(uzbl.scan).v_symbol);
             switch(sym) {
                 case SYM_URI:
                     if(escape_markup) {
                         buf = uzbl.state.uri?
                             g_markup_printf_escaped("%s", uzbl.state.uri):g_strdup("");
                         g_string_append(ret, buf);
                         g_free(buf);
                     }
                     else
                         g_string_append(ret, uzbl.state.uri?
                                 uzbl.state.uri:g_strdup(""));
                     break;
                 case SYM_LOADPRGS:
                     buf = itos(uzbl.gui.sbar.load_progress);
                     g_string_append(ret, buf);
                     g_free(buf);
                     break;
                 case SYM_LOADPRGSBAR:
                     buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
                     g_string_append(ret, buf);
                     g_free(buf);
                     break;
                 case SYM_TITLE:
                     if(escape_markup) {
                         buf = uzbl.gui.main_title?
                             g_markup_printf_escaped("%s", uzbl.gui.main_title):g_strdup("");
                         g_string_append(ret, buf);
                         g_free(buf);
                     }
                     else
                         g_string_append(ret, uzbl.gui.main_title?
                                 uzbl.gui.main_title:g_strdup(""));
                     break;
                 case SYM_SELECTED_URI:
                     if(escape_markup) {
                         buf = uzbl.state.selected_url?
                             g_markup_printf_escaped("%s", uzbl.state.selected_url):g_strdup("");
                         g_string_append(ret, buf);
                         g_free(buf);
                     }
                     else
                         g_string_append(ret, uzbl.state.selected_url?
                                 uzbl.state.selected_url:g_strdup(""));
                     break;
                 case SYM_NAME:
                     buf = itos(uzbl.xwin);
                     g_string_append(ret,
                             uzbl.state.instance_name?uzbl.state.instance_name:buf);
                     g_free(buf);
                     break;
                 case SYM_KEYCMD:
                     if(escape_markup) {
                         buf = uzbl.state.keycmd->str?
                             g_markup_printf_escaped("%s", uzbl.state.keycmd->str):g_strdup("");
                         g_string_append(ret, buf);
                         g_free(buf);
                     }
                     else
                         g_string_append(ret, uzbl.state.keycmd->str?
                                 uzbl.state.keycmd->str:g_strdup(""));
                     break;
                 case SYM_MODE:
                     g_string_append(ret,
                             uzbl.behave.insert_mode?
                             uzbl.behave.insert_indicator:uzbl.behave.cmd_indicator);
                     break;
                 case SYM_MSG:
                     g_string_append(ret,
                             uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
                     break;
                     /* useragent syms */
                 case SYM_WK_MAJ:
                     buf = itos(WEBKIT_MAJOR_VERSION);
                     g_string_append(ret, buf);
                     g_free(buf);
                     break;
                 case SYM_WK_MIN:
                     buf = itos(WEBKIT_MINOR_VERSION);
                     g_string_append(ret, buf);
                     g_free(buf);
                     break;
                 case SYM_WK_MIC:
                     buf = itos(WEBKIT_MICRO_VERSION);
                     g_string_append(ret, buf);
                     g_free(buf);
                     break;
                 case SYM_SYSNAME:
                     g_string_append(ret, uzbl.state.unameinfo.sysname);
                     break;
                 case SYM_NODENAME:
                     g_string_append(ret, uzbl.state.unameinfo.nodename);
                     break;
                 case SYM_KERNREL:
                     g_string_append(ret, uzbl.state.unameinfo.release);
                     break;
                 case SYM_KERNVER:
                     g_string_append(ret, uzbl.state.unameinfo.version);
                     break;
                 case SYM_ARCHSYS:
                     g_string_append(ret, uzbl.state.unameinfo.machine);
                     break;
                 case SYM_ARCHUZBL:
                     g_string_append(ret, ARCH);
                     break;
#ifdef _GNU_SOURCE
                 case SYM_DOMAINNAME:
                     g_string_append(ret, uzbl.state.unameinfo.domainname);
                     break;
#endif
                 case SYM_COMMIT:
                     g_string_append(ret, COMMIT);
                     break;
                 default:
                     break;
             }
         }
         else if(token == G_TOKEN_INT) {
             buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
             g_string_append(ret, buf);
             g_free(buf);
         }
         else if(token == G_TOKEN_IDENTIFIER) {
             g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
         }
         else if(token == G_TOKEN_CHAR) {
             g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
         }
     }

     return g_string_free(ret, FALSE);
}
/* --End Statusbar functions-- */

static void
sharg_append(GArray *a, const gchar *str) {
    const gchar *s = (str ? str : "");
    g_array_append_val(a, s);
}

// make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
static gboolean
run_command (const gchar *command, const guint npre, const gchar **args,
             const gboolean sync, char **output_stdout) {
   //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
    GError *err = NULL;
    
    GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
    gchar *pid = itos(getpid());
    gchar *xwin = itos(uzbl.xwin);
    guint i;
    sharg_append(a, command);
    for (i = 0; i < npre; i++) /* add n args before the default vars */
        sharg_append(a, args[i]);
    sharg_append(a, uzbl.state.config_file);
    sharg_append(a, pid);
    sharg_append(a, xwin);
    sharg_append(a, uzbl.comm.fifo_path);
    sharg_append(a, uzbl.comm.socket_path);
    sharg_append(a, uzbl.state.uri);
    sharg_append(a, uzbl.gui.main_title);

    for (i = npre; i < g_strv_length((gchar**)args); i++)
        sharg_append(a, args[i]);
    
    gboolean result;
    if (sync) {
        if (*output_stdout) *output_stdout = strfree(*output_stdout);
        
        result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
                              NULL, NULL, output_stdout, NULL, NULL, &err);
    } else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
                                  NULL, NULL, NULL, &err);

    if (uzbl.state.verbose) {
        GString *s = g_string_new("spawned:");
        for (i = 0; i < (a->len); i++) {
            gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
            g_string_append_printf(s, " %s", qarg);
            g_free (qarg);
        }
        g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
        printf("%s\n", s->str);
        g_string_free(s, TRUE);
        if(output_stdout) {
            printf("Stdout: %s\n", *output_stdout);
        }
    }
    if (err) {
        g_printerr("error on run_command: %s\n", err->message);
        g_error_free (err);
    }
    g_free (pid);
    g_free (xwin);
    g_array_free (a, TRUE);
    return result;
}

static gchar**
split_quoted(const gchar* src, const gboolean unquote) {
    /* split on unquoted space, return array of strings;
       remove a layer of quotes and backslashes if unquote */
    if (!src) return NULL;
    
    gboolean dq = FALSE;
    gboolean sq = FALSE;
    GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
    GString *s = g_string_new ("");
    const gchar *p;
    gchar **ret;
    gchar *dup;
    for (p = src; *p != '\0'; p++) {
        if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
        else if (*p == '\\') { g_string_append_c(s, *p++);
                               g_string_append_c(s, *p); }
        else if ((*p == '"') && unquote && !sq) dq = !dq;
        else if (*p == '"' && !sq) { g_string_append_c(s, *p);
                                     dq = !dq; }
        else if ((*p == '\'') && unquote && !dq) sq = !sq;
        else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
                                      sq = ! sq; }
        else if ((*p == ' ') && !dq && !sq) {
            dup = g_strdup(s->str);
            g_array_append_val(a, dup);
            g_string_truncate(s, 0);
        } else g_string_append_c(s, *p);
    }
    dup = g_strdup(s->str);
    g_array_append_val(a, dup);
    ret = (gchar**)a->data;
    g_array_free (a, FALSE);
    g_string_free (s, TRUE);
    return ret;
}

static void
spawn(WebKitWebView *web_view, GArray *argv) {
    (void)web_view;
    //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
    if (argv_idx(argv, 0))
        run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))), FALSE, NULL);
}

static void
spawn_sync(WebKitWebView *web_view, GArray *argv) {
    (void)web_view;
    
    if (argv_idx(argv, 0))
        run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))),
                    TRUE, &uzbl.comm.sync_stdout);
}

static void
spawn_sh(WebKitWebView *web_view, GArray *argv) {
    (void)web_view;
    if (!uzbl.behave.shell_cmd) {
        g_printerr ("spawn_sh: shell_cmd is not set!\n");
        return;
    }
    
    guint i;
    gchar *spacer = g_strdup("");
    g_array_insert_val(argv, 1, spacer);
    gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);

    for (i = 1; i < g_strv_length(cmd); i++)
        g_array_prepend_val(argv, cmd[i]);

    if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL);
    g_free (spacer);
    g_strfreev (cmd);
}

static void
spawn_sh_sync(WebKitWebView *web_view, GArray *argv) {
    (void)web_view;
    if (!uzbl.behave.shell_cmd) {
        g_printerr ("spawn_sh_sync: shell_cmd is not set!\n");
        return;
    }
    
    guint i;
    gchar *spacer = g_strdup("");
    g_array_insert_val(argv, 1, spacer);
    gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);

    for (i = 1; i < g_strv_length(cmd); i++)
        g_array_prepend_val(argv, cmd[i]);
         
    if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data,
                         TRUE, &uzbl.comm.sync_stdout);
    g_free (spacer);
    g_strfreev (cmd);
}

static void
parse_command(const char *cmd, const char *param) {
    Command *c;

    if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {

            guint i;
            gchar **par = split_quoted(param, TRUE);
            GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));

            if (c[1] == NOSPLIT) { /* don't split */
                sharg_append(a, param);
            } else if (par) {
                for (i = 0; i < g_strv_length(par); i++)
                    sharg_append(a, par[i]);
            }
            c[0](uzbl.gui.web_view, a);
            g_strfreev (par);
            g_array_free (a, TRUE);

    } else
        g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
}

static void
set_proxy_url() {
    SoupURI *suri;

    if(*uzbl.net.proxy_url == ' '
       || uzbl.net.proxy_url == NULL) {
        soup_session_remove_feature_by_type(uzbl.net.soup_session,
                (GType) SOUP_SESSION_PROXY_URI);
    }
    else {
        suri = soup_uri_new(uzbl.net.proxy_url);
        g_object_set(G_OBJECT(uzbl.net.soup_session),
                SOUP_SESSION_PROXY_URI,
                suri, NULL);
        soup_uri_free(suri);
    }
    return;
}

static void
cmd_load_uri() {
    GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
    g_array_append_val (a, uzbl.state.uri);
    load_uri(uzbl.gui.web_view, a);
    g_array_free (a, TRUE);
}

static void 
cmd_always_insert_mode() {
    uzbl.behave.insert_mode =
        uzbl.behave.always_insert_mode ?  TRUE : FALSE;
    update_title();
}

static void
cmd_max_conns() {
    g_object_set(G_OBJECT(uzbl.net.soup_session),
            SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
}

static void
cmd_max_conns_host() {
    g_object_set(G_OBJECT(uzbl.net.soup_session),
            SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
}

static void
cmd_http_debug() {
    soup_session_remove_feature
        (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
    /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
    /*g_free(uzbl.net.soup_logger);*/

    uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
    soup_session_add_feature(uzbl.net.soup_session,
            SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
}

static WebKitWebSettings*
view_settings() {
    return webkit_web_view_get_settings(uzbl.gui.web_view);
}

static void
cmd_font_size() {
    WebKitWebSettings *ws = view_settings();
    if (uzbl.behave.font_size > 0) {
        g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL);
    }
    
    if (uzbl.behave.monospace_size > 0) {
        g_object_set (G_OBJECT(ws), "default-monospace-font-size",
                      uzbl.behave.monospace_size, NULL);
    } else {
        g_object_set (G_OBJECT(ws), "default-monospace-font-size",
                      uzbl.behave.font_size, NULL);
    }
}

static void
cmd_zoom_level() {
    webkit_web_view_set_zoom_level (uzbl.gui.web_view, uzbl.behave.zoom_level);
}

static void
cmd_disable_plugins() {
    g_object_set (G_OBJECT(view_settings()), "enable-plugins", 
            !uzbl.behave.disable_plugins, NULL);
}

static void
cmd_disable_scripts() {
    g_object_set (G_OBJECT(view_settings()), "enable-scripts",
            !uzbl.behave.disable_scripts, NULL);
}

static void
cmd_minimum_font_size() {
    g_object_set (G_OBJECT(view_settings()), "minimum-font-size",
            uzbl.behave.minimum_font_size, NULL);
}
static void
cmd_autoload_img() {
    g_object_set (G_OBJECT(view_settings()), "auto-load-images",
            uzbl.behave.autoload_img, NULL);
}


static void
cmd_autoshrink_img() {
    g_object_set (G_OBJECT(view_settings()), "auto-shrink-images",
            uzbl.behave.autoshrink_img, NULL);
}


static void
cmd_enable_spellcheck() {
    g_object_set (G_OBJECT(view_settings()), "enable-spell-checking",
            uzbl.behave.enable_spellcheck, NULL);
}

static void
cmd_enable_private() {
    g_object_set (G_OBJECT(view_settings()), "enable-private-browsing",
            uzbl.behave.enable_private, NULL);
}

static void
cmd_print_bg() {
    g_object_set (G_OBJECT(view_settings()), "print-backgrounds",
            uzbl.behave.print_bg, NULL);
}

static void 
cmd_style_uri() {
    g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri",
            uzbl.behave.style_uri, NULL);
}

static void 
cmd_resizable_txt() {
    g_object_set (G_OBJECT(view_settings()), "resizable-text-areas",
            uzbl.behave.resizable_txt, NULL);
}

static void 
cmd_default_encoding() {
    g_object_set (G_OBJECT(view_settings()), "default-encoding",
            uzbl.behave.default_encoding, NULL);
}

static void 
cmd_enforce_96dpi() {
    g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi",
            uzbl.behave.enforce_96dpi, NULL);
}

static void 
cmd_caret_browsing() {
    g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing",
            uzbl.behave.caret_browsing, NULL);
}

static void
cmd_cookie_handler() {
    gchar **split = g_strsplit(uzbl.behave.cookie_handler, " ", 2);
    /* pitfall: doesn't handle chain actions; must the sync_ action manually */
    if ((g_strcmp0(split[0], "sh") == 0) ||
        (g_strcmp0(split[0], "spawn") == 0)) {
        g_free (uzbl.behave.cookie_handler);
        uzbl.behave.cookie_handler =
            g_strdup_printf("sync_%s %s", split[0], split[1]);
    }
    g_strfreev (split);
}

static void
cmd_fifo_dir() {
    uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir);
}

static void
cmd_socket_dir() {
    uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir);
}

static void
cmd_inject_html() {
    if(uzbl.behave.inject_html) {
        webkit_web_view_load_html_string (uzbl.gui.web_view,
                uzbl.behave.inject_html, NULL);
    }
}

static void
cmd_modkey() {
    int i;
    char *buf;

    buf = g_utf8_strup(uzbl.behave.modkey, -1);
    uzbl.behave.modmask = 0;

    if(uzbl.behave.modkey) 
        g_free(uzbl.behave.modkey);
    uzbl.behave.modkey = buf;

    for (i = 0; modkeys[i].key != NULL; i++) {
        if (g_strrstr(buf, modkeys[i].key))
            uzbl.behave.modmask |= modkeys[i].mask;
    }
}

static void
cmd_useragent() {
    if (*uzbl.net.useragent == ' ') {
        g_free (uzbl.net.useragent);
        uzbl.net.useragent = NULL;
    } else {
        gchar *ua = expand_template(uzbl.net.useragent, FALSE);
        if (ua)
            g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
        g_free(uzbl.net.useragent);
        uzbl.net.useragent = ua;
    }
}

static void
move_statusbar() {
    gtk_widget_ref(uzbl.gui.scrolled_win);
    gtk_widget_ref(uzbl.gui.mainbar);
    gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
    gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);

    if(uzbl.behave.status_top) {
        gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
    }
    else {
        gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
    }
    gtk_widget_unref(uzbl.gui.scrolled_win);
    gtk_widget_unref(uzbl.gui.mainbar);
    gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
    return;
}

static gboolean
set_var_value(gchar *name, gchar *val) {
    uzbl_cmdprop *c = NULL;
    char *endp = NULL;
    char *buf = NULL;

    if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
        /* check for the variable type */
        if (c->type == TYPE_STR) {
            buf = expand_vars(val);
            g_free(*c->ptr);
            *c->ptr = buf;
        } else if(c->type == TYPE_INT) {
            int *ip = (int *)c->ptr;
            buf = expand_vars(val);
            *ip = (int)strtoul(buf, &endp, 10);
            g_free(buf);
        } else if (c->type == TYPE_FLOAT) {
            float *fp = (float *)c->ptr;
            buf = expand_vars(val);
            *fp = strtod(buf, &endp);
            g_free(buf);
        }

        /* invoke a command specific function */
        if(c->func) c->func();
    }
    return TRUE;
}

static void
render_html() {
    Behaviour *b = &uzbl.behave;

    if(b->html_buffer->str) {
        webkit_web_view_load_html_string (uzbl.gui.web_view,
                b->html_buffer->str, b->base_url);
        g_string_free(b->html_buffer, TRUE);
        b->html_buffer = g_string_new("");
    }
}

enum {M_CMD, M_HTML};
static void
parse_cmd_line(const char *ctl_line) {
    Behaviour *b = &uzbl.behave;
    size_t len=0;

    if(b->mode == M_HTML) {
        len = strlen(b->html_endmarker);
        /* ctl_line has trailing '\n' so we check for strlen(ctl_line)-1 */
        if(len == strlen(ctl_line)-1 &&
           !strncmp(b->html_endmarker, ctl_line, len)) {
            set_timeout(0);
            set_var_value("mode", "0");
            render_html();
            return;
        }
        else {
            set_timeout(b->html_timeout);
            g_string_append(b->html_buffer, ctl_line);
        }
    }
    else if((ctl_line[0] == '#') /* Comments */
            || (ctl_line[0] == ' ')
            || (ctl_line[0] == '\n'))
        ; /* ignore these lines */
    else { /* parse a command */
        gchar *ctlstrip;
        gchar **tokens = NULL;
        len = strlen(ctl_line);

        if (ctl_line[len - 1] == '\n') /* strip trailing newline */
            ctlstrip = g_strndup(ctl_line, len - 1);
        else ctlstrip = g_strdup(ctl_line);

        tokens = g_strsplit(ctlstrip, " ", 2);
        parse_command(tokens[0], tokens[1]);
        g_free(ctlstrip);
        g_strfreev(tokens);
    }
}

static gchar*
build_stream_name(int type, const gchar* dir) {
    char *xwin_str;
    State *s = &uzbl.state;
    gchar *str;

    xwin_str = itos((int)uzbl.xwin);
    if (type == FIFO) {
        str = g_strdup_printf
            ("%s/uzbl_fifo_%s", dir,
             s->instance_name ? s->instance_name : xwin_str);
    } else if (type == SOCKET) {
        str = g_strdup_printf
            ("%s/uzbl_socket_%s", dir,
             s->instance_name ? s->instance_name : xwin_str );
    }
    g_free(xwin_str);
    return str;
}

static gboolean
control_fifo(GIOChannel *gio, GIOCondition condition) {
    if (uzbl.state.verbose)
        printf("triggered\n");
    gchar *ctl_line;
    GIOStatus ret;
    GError *err = NULL;

    if (condition & G_IO_HUP)
        g_error ("Fifo: Read end of pipe died!\n");

    if(!gio)
       g_error ("Fifo: GIOChannel broke\n");

    ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
    if (ret == G_IO_STATUS_ERROR) {
        g_error ("Fifo: Error reading: %s\n", err->message);
        g_error_free (err);
    }

    parse_cmd_line(ctl_line);
    g_free(ctl_line);

    return TRUE;
}

static gchar*
init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
    if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
        if (unlink(uzbl.comm.fifo_path) == -1)
            g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
        g_free(uzbl.comm.fifo_path);
        uzbl.comm.fifo_path = NULL;
    }

    if (*dir == ' ') { /* space unsets the variable */
        g_free (dir);
        return NULL;
    }

    GIOChannel *chan = NULL;
    GError *error = NULL;
    gchar *path = build_stream_name(FIFO, dir);

    if (!file_exists(path)) {
        if (mkfifo (path, 0666) == 0) {
            // 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.
            chan = g_io_channel_new_file(path, "r+", &error);
            if (chan) {
                if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
                    if (uzbl.state.verbose)
                        printf ("init_fifo: created successfully as %s\n", path);
                    uzbl.comm.fifo_path = path;
                    return dir;
                } else g_warning ("init_fifo: could not add watch on %s\n", path);
            } else g_warning ("init_fifo: can't open: %s\n", error->message);
        } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
    } else g_warning ("init_fifo: can't create %s: file exists\n", path);

    /* if we got this far, there was an error; cleanup */
    if (error) g_error_free (error);
    g_free(dir);
    g_free(path);
    return NULL;
}

static gboolean
control_stdin(GIOChannel *gio, GIOCondition condition) {
    (void) condition;
    gchar *ctl_line = NULL;
    GIOStatus ret;

    ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
    if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
        return FALSE;

    parse_cmd_line(ctl_line);
    g_free(ctl_line);

    return TRUE;
}

static void
create_stdin () {
    GIOChannel *chan = NULL;
    GError *error = NULL;

    chan = g_io_channel_unix_new(fileno(stdin));
    if (chan) {
        if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
            g_error ("Stdin: could not add watch\n");
        } else {
            if (uzbl.state.verbose)
                printf ("Stdin: watch added successfully\n");
        }
    } else {
        g_error ("Stdin: Error while opening: %s\n", error->message);
    }
    if (error) g_error_free (error);
}

static gboolean
control_socket(GIOChannel *chan) {
    struct sockaddr_un remote;
    char buffer[512], *ctl_line;
    char temp[128];
    int sock, clientsock, n, done;
    unsigned int t;

    sock = g_io_channel_unix_get_fd(chan);

    memset (buffer, 0, sizeof (buffer));

    t          = sizeof (remote);
    clientsock = accept (sock, (struct sockaddr *) &remote, &t);

    done = 0;
    do {
        memset (temp, 0, sizeof (temp));
        n = recv (clientsock, temp, 128, 0);
        if (n == 0) {
            buffer[strlen (buffer)] = '\0';
            done = 1;
        }
        if (!done)
            strcat (buffer, temp);
    } while (!done);

    if (strcmp (buffer, "\n") < 0) {
        buffer[strlen (buffer) - 1] = '\0';
    } else {
        buffer[strlen (buffer)] = '\0';
    }
    close (clientsock);
    ctl_line = g_strdup(buffer);
    parse_cmd_line (ctl_line);

/*
   TODO: we should be able to do it with this.  but glib errors out with "Invalid argument"
    GError *error = NULL;
    gsize len;
    GIOStatus ret;
    ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
    if (ret == G_IO_STATUS_ERROR)
        g_error ("Error reading: %s\n", error->message);

    printf("Got line %s (%u bytes) \n",ctl_line, len);
    if(ctl_line) {
       parse_line(ctl_line);
*/

    g_free(ctl_line);
    return TRUE;
}

static gchar*
init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
    if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
        if (unlink(uzbl.comm.socket_path) == -1)
            g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
        g_free(uzbl.comm.socket_path);
        uzbl.comm.socket_path = NULL;
    }

    if (*dir == ' ') {
        g_free(dir);
        return NULL;
    }

    GIOChannel *chan = NULL;
    int sock, len;
    struct sockaddr_un local;
    gchar *path = build_stream_name(SOCKET, dir);

    sock = socket (AF_UNIX, SOCK_STREAM, 0);

    local.sun_family = AF_UNIX;
    strcpy (local.sun_path, path);
    unlink (local.sun_path);

    len = strlen (local.sun_path) + sizeof (local.sun_family);
    if (bind (sock, (struct sockaddr *) &local, len) != -1) {
        if (uzbl.state.verbose)
            printf ("init_socket: opened in %s\n", path);
        listen (sock, 5);

        if( (chan = g_io_channel_unix_new(sock)) ) {
            g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
            uzbl.comm.socket_path = path;
            return dir;
        }
    } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));

    /* if we got this far, there was an error; cleanup */
    g_free(path);
    g_free(dir);
    return NULL;
}

/*
 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
*/
// this function may be called very early when the templates are not set (yet), hence the checks
static void
update_title (void) {
    Behaviour *b = &uzbl.behave;
    gchar *parsed;

    if (b->show_status) {
        if (b->title_format_short) {
            parsed = expand_template(b->title_format_short, FALSE);
            gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
            g_free(parsed);
        }
        if (b->status_format) {
            parsed = expand_template(b->status_format, TRUE);
            gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
            g_free(parsed);
        }
        if (b->status_background) {
            GdkColor color;
            gdk_color_parse (b->status_background, &color);
            //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)
            gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
        }
    } else {
        if (b->title_format_long) {
            parsed = expand_template(b->title_format_long, FALSE);
            gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
            g_free(parsed);
        }
    }
}

static gboolean
key_press_cb (GtkWidget* window, GdkEventKey* event)
{
    //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.

    (void) window;

    if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
        || 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)
        return FALSE;

    /* turn off insert mode (if always_insert_mode is not used) */
    if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
        uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
        update_title();
        return TRUE;
    }

    if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
        return FALSE;

    if (event->keyval == GDK_Escape) {
        g_string_truncate(uzbl.state.keycmd, 0);
        update_title();
        dehilight(uzbl.gui.web_view, NULL);
        return TRUE;
    }

    //Insert without shift - insert from clipboard; Insert with shift - insert from primary
    if (event->keyval == GDK_Insert) {
        gchar * str;
        if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
            str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
        } else {
            str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
        }
        if (str) {
            g_string_append (uzbl.state.keycmd, str);
            update_title ();
            g_free (str);
        }
        return TRUE;
    }

    if (event->keyval == GDK_BackSpace)
        keycmd_bs(NULL, NULL);

    gboolean key_ret = FALSE;
    if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
        key_ret = TRUE;
    if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);

    run_keycmd(key_ret);
    update_title();
    if (key_ret) return (!uzbl.behave.insert_mode);
    return TRUE;
}

static void
run_keycmd(const gboolean key_ret) {
    /* run the keycmd immediately if it isn't incremental and doesn't take args */
    Action *act;
    if ((act = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
        g_string_truncate(uzbl.state.keycmd, 0);
        parse_command(act->name, act->param);
        return;
    }

    /* try if it's an incremental keycmd or one that takes args, and run it */
    GString* short_keys = g_string_new ("");
    GString* short_keys_inc = g_string_new ("");
    guint i;
    for (i=0; i<(uzbl.state.keycmd->len); i++) {
        g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
        g_string_assign(short_keys_inc, short_keys->str);
        g_string_append_c(short_keys, '_');
        g_string_append_c(short_keys_inc, '*');

        if (key_ret && (act = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
            /* run normal cmds only if return was pressed */
            exec_paramcmd(act, i);
            g_string_truncate(uzbl.state.keycmd, 0);
            break;
        } else if ((act = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
            if (key_ret)  /* just quit the incremental command on return */
                g_string_truncate(uzbl.state.keycmd, 0);
            else exec_paramcmd(act, i); /* otherwise execute the incremental */
            break;
        }
        
        g_string_truncate(short_keys, short_keys->len - 1);
    }
    g_string_free (short_keys, TRUE);
    g_string_free (short_keys_inc, TRUE);
}

static void
exec_paramcmd(const Action *act, const guint i) {
    GString *parampart = g_string_new (uzbl.state.keycmd->str);
    GString *actionname = g_string_new ("");
    GString *actionparam = g_string_new ("");
    g_string_erase (parampart, 0, i+1);
    if (act->name)
        g_string_printf (actionname, act->name, parampart->str);
    if (act->param)
        g_string_printf (actionparam, act->param, parampart->str);
    parse_command(actionname->str, actionparam->str);
    g_string_free(actionname, TRUE);
    g_string_free(actionparam, TRUE);
    g_string_free(parampart, TRUE);
}


static GtkWidget*
create_browser () {
    GUI *g = &uzbl.gui;

    GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    //main_window_ref = g_object_ref(scrolled_window);
    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

    g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
    gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));

    g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
    g_signal_connect (G_OBJECT (g->web_view), "mime-type-policy-decision-requested", G_CALLBACK (mime_policy_cb), g->web_view);

    return scrolled_window;
}

static GtkWidget*
create_mainbar () {
    GUI *g = &uzbl.gui;

    g->mainbar = gtk_hbox_new (FALSE, 0);

    /* keep a reference to the bar so we can re-pack it at runtime*/
    //sbar_ref = g_object_ref(g->mainbar);

    g->mainbar_label = gtk_label_new ("");
    gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
    gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
    gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
    gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
    gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
    g_signal_connect (G_OBJECT (g->mainbar), "key-press-event", G_CALLBACK (key_press_cb), NULL);
    return g->mainbar;
}

static
GtkWidget* create_window () {
    GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
    gtk_widget_set_name (window, "Uzbl browser");
    g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
    g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);

    return window;
}

static gchar**
inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *newargs) {
    /*
      If actname is one that calls an external command, this function will inject
      newargs in front of the user-provided args in that command line.  They will
      come become after the body of the script (in sh) or after the name of
      the command to execute (in spawn).
      i.e. sh <body> <userargs> becomes sh <body> <ARGS> <userargs> and
      span <command> <userargs> becomes spawn <command> <ARGS> <userargs>.

      The return value consist of two strings: the action (sh, ...) and its args.

      If act is not one that calls an external command, then the given action merely
      gets duplicated.
    */
    GArray *rets = g_array_new(TRUE, FALSE, sizeof(gchar*));
    gchar *actdup = g_strdup(actname);
    g_array_append_val(rets, actdup);

    if ((g_strcmp0(actname, "spawn") == 0) ||
        (g_strcmp0(actname, "sh") == 0) ||
        (g_strcmp0(actname, "sync_spawn") == 0) ||
        (g_strcmp0(actname, "sync_sh") == 0)) {
        guint i;
        GString *a = g_string_new("");
        gchar **spawnparts = split_quoted(origargs, FALSE);
        g_string_append_printf(a, "%s", spawnparts[0]); /* sh body or script name */
        if (newargs) g_string_append_printf(a, " %s", newargs); /* handler args */

        for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
            if (spawnparts[i]) g_string_append_printf(a, " %s", spawnparts[i]);

        g_array_append_val(rets, a->str);
        g_string_free(a, FALSE);
        g_strfreev(spawnparts);
    } else {
        gchar *origdup = g_strdup(origargs);
        g_array_append_val(rets, origdup);
    }
    return (gchar**)g_array_free(rets, FALSE);
}

static void
run_handler (const gchar *act, const gchar *args) {
    /* Consider this code a temporary hack to make the handlers usable.
       In practice, all this splicing, injection, and reconstruction is
       inefficient, annoying and hard to manage.  Potential pitfalls arise
       when the handler specific args 1) are not quoted  (the handler
       callbacks should take care of this)  2) are quoted but interfere
       with the users' own quotation.  A more ideal solution is
       to refactor parse_command so that it doesn't just take a string
       and execute it; rather than that, we should have a function which
       returns the argument vector parsed from the string.  This vector
       could be modified (e.g. insert additional args into it) before
       passing it to the next function that actually executes it.  Though
       it still isn't perfect for chain actions..  will reconsider & re-
       factor when I have the time. -duc */

    char **parts = g_strsplit(act, " ", 2);
    if (!parts) return;
    if (g_strcmp0(parts[0], "chain") == 0) {
        GString *newargs = g_string_new("");
        gchar **chainparts = split_quoted(parts[1], FALSE);
        
        /* for every argument in the chain, inject the handler args
           and make sure the new parts are wrapped in quotes */
        gchar **cp = chainparts;
        gchar quot = '\'';
        gchar *quotless = NULL;
        gchar **spliced_quotless = NULL; // sigh -_-;
        gchar **inpart = NULL;
        
        while (*cp) {
            if ((**cp == '\'') || (**cp == '\"')) { /* strip old quotes */
                quot = **cp;
                quotless = g_strndup(&(*cp)[1], strlen(*cp) - 2);
            } else quotless = g_strdup(*cp);

            spliced_quotless = g_strsplit(quotless, " ", 2);
            inpart = inject_handler_args(spliced_quotless[0], spliced_quotless[1], args);
            g_strfreev(spliced_quotless);
            
            g_string_append_printf(newargs, " %c%s %s%c", quot, inpart[0], inpart[1], quot);
            g_free(quotless);
            g_strfreev(inpart);
            cp++;
        }

        parse_command(parts[0], &(newargs->str[1]));
        g_string_free(newargs, TRUE);
        g_strfreev(chainparts);
        
    } else {
        gchar **inparts = inject_handler_args(parts[0], parts[1], args);
        parse_command(inparts[0], inparts[1]);
        g_free(inparts[0]);
        g_free(inparts[1]);
    }
    g_strfreev(parts);
}

static void
add_binding (const gchar *key, const gchar *act) {
    char **parts = g_strsplit(act, " ", 2);
    Action *action;

    if (!parts)
        return;

    //Debug:
    if (uzbl.state.verbose)
        printf ("Binding %-10s : %s\n", key, act);
    action = new_action(parts[0], parts[1]);

    if (g_hash_table_remove (uzbl.bindings, key))
        g_warning ("Overwriting existing binding for \"%s\"", key);
    g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
    g_strfreev(parts);
}

static gchar*
get_xdg_var (XDG_Var xdg) {
    const gchar* actual_value = getenv (xdg.environmental);
    const gchar* home         = getenv ("HOME");

    gchar* return_value = str_replace ("~", home, actual_value);

    if (! actual_value || strcmp (actual_value, "") == 0) {
        if (xdg.default_value) {
            return_value = str_replace ("~", home, xdg.default_value);
        } else {
            return_value = NULL;
        }
    }
    return return_value;
}

static gchar*
find_xdg_file (int xdg_type, char* filename) {
    /* xdg_type = 0 => config
       xdg_type = 1 => data
       xdg_type = 2 => cache*/

    gchar* xdgv = get_xdg_var (XDG[xdg_type]);
    gchar* temporary_file = g_strconcat (xdgv, filename, NULL);
    g_free (xdgv);

    gchar* temporary_string;
    char*  saveptr;
    char*  buf;

    if (! file_exists (temporary_file) && xdg_type != 2) {
        buf = get_xdg_var (XDG[3 + xdg_type]);
        temporary_string = (char *) strtok_r (buf, ":", &saveptr);
        g_free(buf);

        while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) {
            g_free (temporary_file);
            temporary_file = g_strconcat (temporary_string, filename, NULL);
        }
    }
    
    //g_free (temporary_string); - segfaults.

    if (file_exists (temporary_file)) {
        return temporary_file;
    } else {
        return NULL;
    }
}
static void
settings_init () {
    State *s = &uzbl.state;
    Network *n = &uzbl.net;
    int i;
    for (i = 0; default_config[i].command != NULL; i++) {
        parse_cmd_line(default_config[i].command);
    }

    if (!s->config_file) {
        s->config_file = find_xdg_file (0, "/uzbl/config");
    }

    if (s->config_file) {
        GArray* lines = read_file_by_line (s->config_file);
        int i = 0;
        gchar* line;

        while ((line = g_array_index(lines, gchar*, i))) {
            parse_cmd_line (line);
            i ++;
            g_free (line);
        }
        g_array_free (lines, TRUE);
    } else {
        if (uzbl.state.verbose)
            printf ("No configuration file loaded.\n");
    }

    g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL);
}

static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
    (void) session;
    (void) user_data;
    if (!uzbl.behave.cookie_handler)
         return;

    soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
    GString *s = g_string_new ("");
    SoupURI * soup_uri = soup_message_get_uri(msg);
    g_string_printf(s, "GET '%s' '%s'", soup_uri->host, soup_uri->path);
    run_handler(uzbl.behave.cookie_handler, s->str);

    if(uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) {
        char *p = strchr(uzbl.comm.sync_stdout, '\n' );
        if ( p != NULL ) *p = '\0';
        soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout);
    }
    if (uzbl.comm.sync_stdout)
        uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout);
        
    g_string_free(s, TRUE);
}

static void
save_cookies (SoupMessage *msg, gpointer user_data){
    (void) user_data;
    GSList *ck;
    char *cookie;
    for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
        cookie = soup_cookie_to_set_cookie_header(ck->data);
        SoupURI * soup_uri = soup_message_get_uri(msg);
        GString *s = g_string_new ("");
        g_string_printf(s, "PUT '%s' '%s' '%s'", soup_uri->host, soup_uri->path, cookie);
        run_handler(uzbl.behave.cookie_handler, s->str);
        g_free (cookie);
        g_string_free(s, TRUE);
    }
    g_slist_free(ck);
}

/* --- WEBINSPECTOR --- */
static void
hide_window_cb(GtkWidget *widget, gpointer data) {
    (void) data;

    gtk_widget_hide(widget);
}

static WebKitWebView*
create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
    (void) data;
    (void) page;
    (void) web_inspector;
    GtkWidget* scrolled_window;
    GtkWidget* new_web_view;
    GUI *g = &uzbl.gui;

    g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(G_OBJECT(g->inspector_window), "delete-event",
            G_CALLBACK(hide_window_cb), NULL);

    gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector");
    gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300);
    gtk_widget_show(g->inspector_window);

    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
            GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window);
    gtk_widget_show(scrolled_window);

    new_web_view = webkit_web_view_new();
    gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view);

    return WEBKIT_WEB_VIEW(new_web_view);
}

static gboolean
inspector_show_window_cb (WebKitWebInspector* inspector){
    (void) inspector;
    gtk_widget_show(uzbl.gui.inspector_window);
    return TRUE;
}

/* TODO: Add variables and code to make use of these functions */
static gboolean
inspector_close_window_cb (WebKitWebInspector* inspector){
    (void) inspector;
    return TRUE;
}

static gboolean
inspector_attach_window_cb (WebKitWebInspector* inspector){
    (void) inspector;
    return FALSE;
}

static gboolean
inspector_dettach_window_cb (WebKitWebInspector* inspector){
    (void) inspector;
    return FALSE;
}

static gboolean
inspector_uri_changed_cb (WebKitWebInspector* inspector){
    (void) inspector;
    return FALSE;
}

static gboolean
inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
    (void) inspector;
    return FALSE;
}

static void
set_up_inspector() {
    GUI *g = &uzbl.gui;
    WebKitWebSettings *settings = view_settings();
    g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);

    uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view);
    g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL);
    g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL);
    g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL);
    g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL);
    g_signal_connect (G_OBJECT (g->inspector), "dettach-window", G_CALLBACK (inspector_dettach_window_cb), NULL);
    g_signal_connect (G_OBJECT (g->inspector), "destroy", G_CALLBACK (inspector_inspector_destroyed_cb), NULL);

    g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL);
}

static void
dump_var_hash(gpointer k, gpointer v, gpointer ud) {
    (void) ud;
    uzbl_cmdprop *c = v;

    if(!c->dump)
        return;

    if(c->type == TYPE_STR)
        printf("set %s = %s\n", (char *)k, *c->ptr?(char *)*c->ptr:" ");
    else if(c->type == TYPE_INT)
        printf("set %s = %d\n", (char *)k, (int)*c->ptr);
}

static void
dump_key_hash(gpointer k, gpointer v, gpointer ud) {
    (void) ud;
    Action *a = v;

    printf("bind %s = %s %s\n", (char *)k ,
            (char *)a->name, a->param?(char *)a->param:"");
}

static void
dump_config() {
    g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL);
    g_hash_table_foreach(uzbl.bindings, dump_key_hash, NULL);
}

/** -- MAIN -- **/
int
main (int argc, char* argv[]) {
    gtk_init (&argc, &argv);
    if (!g_thread_supported ())
        g_thread_init (NULL);
    uzbl.state.executable_path = g_strdup(argv[0]);
    uzbl.state.selected_url = NULL;
    uzbl.state.searchtx = NULL;

    GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
    g_option_context_add_main_entries (context, entries, NULL);
    g_option_context_add_group (context, gtk_get_option_group (TRUE));
    g_option_context_parse (context, &argc, &argv, NULL);
    g_option_context_free(context);
    
    gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL);
    gboolean verbose_override = uzbl.state.verbose;

    /* initialize hash table */
    uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);

    uzbl.net.soup_session = webkit_get_default_session();
    uzbl.state.keycmd = g_string_new("");

    if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
        fprintf(stderr, "uzbl: error hooking SIGTERM\n");
    if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
        fprintf(stderr, "uzbl: error hooking SIGINT\n");
    if(setup_signal(SIGALRM, catch_alrm) == SIG_ERR)
        fprintf(stderr, "uzbl: error hooking SIGALARM\n");


    if(uname(&uzbl.state.unameinfo) == -1)
        g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");

    uzbl.gui.sbar.progress_s = g_strdup("=");
    uzbl.gui.sbar.progress_u = g_strdup("·");
    uzbl.gui.sbar.progress_w = 10;

    /* HTML mode defaults*/
    uzbl.behave.html_buffer = g_string_new("");
    uzbl.behave.html_endmarker = g_strdup(".");
    uzbl.behave.html_timeout = 60;
    uzbl.behave.base_url = g_strdup("http://invalid");

    /* default mode indicators */
    uzbl.behave.insert_indicator = g_strdup("I");
    uzbl.behave.cmd_indicator    = g_strdup("C");

    setup_scanner();
    commands_hash ();
    make_var_to_name_hash();

    uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);

    uzbl.gui.scrolled_win = create_browser();
    create_mainbar();

    /* initial packing */
    gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);

    uzbl.gui.main_window = create_window ();
    gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);


    gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
    gtk_widget_show_all (uzbl.gui.main_window);
    uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);

    if (uzbl.state.verbose) {
        printf("Uzbl start location: %s\n", argv[0]);
        printf("window_id %i\n",(int) uzbl.xwin);
        printf("pid %i\n", getpid ());
        printf("name: %s\n", uzbl.state.instance_name);
    }

    uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
    uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
    uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
    uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
    gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);

    settings_init ();

    if (!uzbl.behave.show_status)
        gtk_widget_hide(uzbl.gui.mainbar);
    else
        update_title();

    /* WebInspector */
    set_up_inspector();

    create_stdin();

    if (verbose_override > uzbl.state.verbose)
        uzbl.state.verbose = verbose_override;
    
    if (uri_override) {
        set_var_value("uri", uri_override);
        g_free(uri_override);
    } else if (uzbl.state.uri)
        cmd_load_uri(uzbl.gui.web_view, NULL);

    gtk_main ();
    clean_up();

    return EXIT_SUCCESS;
}

/* vi: set et ts=4: */
