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