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