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