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