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