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