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